Package taskcoachlib :: Package gui :: Package uicommand :: Module uicommand
[hide private]
[frames] | no frames]

Source Code for Module taskcoachlib.gui.uicommand.uicommand

   1  # -*- coding: utf-8 -*- 
   2   
   3  ''' 
   4  Task Coach - Your friendly task manager 
   5  Copyright (C) 2004-2013 Task Coach developers <developers@taskcoach.org> 
   6  Copyright (C) 2008 Rob McMullen <rob.mcmullen@gmail.com> 
   7  Copyright (C) 2013 Marcus Johansson <marcus1.johansson@gmail.com> 
   8   
   9  Task Coach is free software: you can redistribute it and/or modify 
  10  it under the terms of the GNU General Public License as published by 
  11  the Free Software Foundation, either version 3 of the License, or 
  12  (at your option) any later version. 
  13   
  14  Task Coach is distributed in the hope that it will be useful, 
  15  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  17  GNU General Public License for more details. 
  18   
  19  You should have received a copy of the GNU General Public License 
  20  along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  21  ''' 
  22   
  23  import wx 
  24   
  25  from taskcoachlib import patterns, meta, command, help, widgets, persistence, \ 
  26      thirdparty, render, operating_system  # pylint: disable=W0622 
  27  from taskcoachlib.command import quickAddParser 
  28  from taskcoachlib.domain import base, task, note, category, attachment, \ 
  29      effort, date 
  30  from taskcoachlib.gui import dialog, printer 
  31  from taskcoachlib.gui.wizard import CSVImportWizard 
  32  from taskcoachlib.i18n import _ 
  33  from taskcoachlib.mailer import sendMail 
  34  from taskcoachlib.thirdparty import desktop, hypertreelist 
  35  from taskcoachlib.thirdparty.pubsub import pub 
  36  from taskcoachlib.thirdparty.wxScheduler import wxSCHEDULER_NEXT, \ 
  37      wxSCHEDULER_PREV, wxSCHEDULER_TODAY 
  38  from taskcoachlib.tools import anonymize 
  39  from taskcoachlib.workarounds import ExceptionAsUnicode 
  40  import base_uicommand 
  41  import mixin_uicommand 
  42  import settings_uicommand 
43 44 45 -class IOCommand(base_uicommand.UICommand): # pylint: disable=W0223
46 - def __init__(self, *args, **kwargs):
47 self.iocontroller = kwargs.pop('iocontroller', None) 48 super(IOCommand, self).__init__(*args, **kwargs)
49
50 51 -class TaskListCommand(base_uicommand.UICommand): # pylint: disable=W0223
52 - def __init__(self, *args, **kwargs):
53 self.taskList = kwargs.pop('taskList', None) 54 super(TaskListCommand, self).__init__(*args, **kwargs)
55
56 57 -class EffortListCommand(base_uicommand.UICommand): # pylint: disable=W0223
58 - def __init__(self, *args, **kwargs):
59 self.effortList = kwargs.pop('effortList', None) 60 super(EffortListCommand, self).__init__(*args, **kwargs)
61
62 63 -class CategoriesCommand(base_uicommand.UICommand): # pylint: disable=W0223
64 - def __init__(self, *args, **kwargs):
65 self.categories = kwargs.pop('categories', None) 66 super(CategoriesCommand, self).__init__(*args, **kwargs)
67
68 69 -class NotesCommand(base_uicommand.UICommand): # pylint: disable=W0223
70 - def __init__(self, *args, **kwargs):
71 self.notes = kwargs.pop('notes', None) 72 super(NotesCommand, self).__init__(*args, **kwargs)
73
74 75 -class AttachmentsCommand(base_uicommand.UICommand): # pylint: disable=W0223
76 - def __init__(self, *args, **kwargs):
77 self.attachments = kwargs.pop('attachments', None) 78 super(AttachmentsCommand, self).__init__(*args, **kwargs)
79
80 81 -class ViewerCommand(base_uicommand.UICommand): # pylint: disable=W0223
82 - def __init__(self, *args, **kwargs):
83 self.viewer = kwargs.pop('viewer', None) 84 super(ViewerCommand, self).__init__(*args, **kwargs)
85
86 - def __eq__(self, other):
87 return super(ViewerCommand, self).__eq__(other) and \ 88 self.viewer.settingsSection() == other.viewer.settingsSection()
89
90 # Commands: 91 92 -class FileOpen(IOCommand):
93 - def __init__(self, *args, **kwargs):
94 super(FileOpen, self).__init__(menuText=_('&Open...\tCtrl+O'), 95 helpText=help.fileOpen, bitmap='fileopen', id=wx.ID_OPEN, 96 *args, **kwargs)
97
98 - def doCommand(self, event):
99 self.iocontroller.open()
100
101 102 -class RecentFileOpen(IOCommand):
103 - def __init__(self, *args, **kwargs):
104 self.__filename = kwargs.pop('filename') 105 index = kwargs.pop('index') 106 super(RecentFileOpen, self).__init__( \ 107 menuText='%d %s' % (index, self.__filename), 108 helpText=_('Open %s') % self.__filename, *args, **kwargs)
109
110 - def doCommand(self, event):
111 self.iocontroller.open(self.__filename)
112
113 114 -class FileMerge(IOCommand):
115 - def __init__(self, *args, **kwargs):
116 super(FileMerge, self).__init__(menuText=_('&Merge...'), 117 helpText=_('Merge tasks from another file with the current file'), 118 bitmap='merge', *args, **kwargs)
119
120 - def doCommand(self, event):
121 self.iocontroller.merge()
122
123 124 -class FileClose(IOCommand):
125 - def __init__(self, *args, **kwargs):
126 super(FileClose, self).__init__(menuText=_('&Close\tCtrl+W'), 127 helpText=help.fileClose, bitmap='close', id=wx.ID_CLOSE, 128 *args, **kwargs)
129
130 - def doCommand(self, event):
131 self.mainWindow().closeEditors() 132 self.iocontroller.close()
133
134 135 -class FileSave(IOCommand):
136 - def __init__(self, *args, **kwargs):
137 super(FileSave, self).__init__(menuText=_('&Save\tCtrl+S'), 138 helpText=help.fileSave, bitmap='save', id=wx.ID_SAVE, 139 *args, **kwargs)
140
141 - def doCommand(self, event):
142 self.iocontroller.save()
143
144 - def enabled(self, event):
145 return self.iocontroller.needSave()
146
147 148 -class FileMergeDiskChanges(IOCommand):
149 - def __init__(self, *args, **kwargs):
150 super(FileMergeDiskChanges, self).__init__(menuText=_('Merge &disk changes\tShift-Ctrl-M'), 151 helpText=help.fileMergeDiskChanges, bitmap='mergedisk', 152 *args, **kwargs)
153
154 - def doCommand(self, event):
156
157 - def enabled(self, event):
158 return self.iocontroller.changedOnDisk()
159
160 161 -class FileSaveAs(IOCommand):
162 - def __init__(self, *args, **kwargs):
163 super(FileSaveAs, self).__init__( \ 164 menuText=_('S&ave as...\tShift+Ctrl+S'), 165 helpText=help.fileSaveAs, bitmap='saveas', id=wx.ID_SAVEAS, 166 *args, **kwargs)
167
168 - def doCommand(self, event):
169 self.iocontroller.saveas()
170
171 172 -class FileSaveSelection(mixin_uicommand.NeedsSelectedTasksMixin, IOCommand, 173 ViewerCommand):
174 - def __init__(self, *args, **kwargs):
175 super(FileSaveSelection, self).__init__( \ 176 menuText=_('Sa&ve selected tasks to new taskfile...'), 177 helpText=_('Save the selected tasks to a separate taskfile'), 178 bitmap='saveselection', *args, **kwargs)
179
180 - def doCommand(self, event):
182
183 184 -class FileSaveSelectedTaskAsTemplate(mixin_uicommand.NeedsOneSelectedTaskMixin, 185 IOCommand, ViewerCommand):
186 - def __init__(self, *args, **kwargs):
187 super(FileSaveSelectedTaskAsTemplate, self).__init__( \ 188 menuText=_('Save selected task as &template'), 189 helpText=_('Save the selected task as a task template'), 190 bitmap='saveselection', *args, **kwargs)
191
192 - def doCommand(self, event):
194
195 196 -class FileImportTemplate(IOCommand):
197 - def __init__(self, *args, **kwargs):
198 super(FileImportTemplate, self).__init__( \ 199 menuText=_('&Import template...'), 200 helpText=_('Import a new template from a template file'), 201 bitmap='fileopen', *args, **kwargs)
202
203 - def doCommand(self, event):
205
206 207 -class FileEditTemplates(settings_uicommand.SettingsCommand, 208 base_uicommand.UICommand):
209 - def __init__(self, *args, **kwargs):
210 super(FileEditTemplates, self).__init__( \ 211 menuText=_('Edit templates...'), 212 helpText=_('Edit existing templates'), *args, **kwargs)
213
214 - def doCommand(self, event):
215 templateDialog = dialog.templates.TemplatesDialog(self.settings, 216 self.mainWindow(), title=_('Edit templates')) 217 templateDialog.Show()
218
219 220 -class FilePurgeDeletedItems(mixin_uicommand.NeedsDeletedItemsMixin, IOCommand):
221 - def __init__(self, *args, **kwargs):
222 super(FilePurgeDeletedItems, self).__init__( \ 223 menuText=_('&Purge deleted items'), 224 helpText=_('Actually delete deleted tasks and notes ' 225 '(see the SyncML chapter in Help)'), 226 bitmap='delete', *args, **kwargs)
227
228 - def doCommand(self, event):
229 if (wx.MessageBox(_('''Purging deleted items is undoable. 230 If you're planning on enabling 231 the SyncML feature again with the 232 same server you used previously, 233 these items will probably come back. 234 235 Do you still want to purge?'''), 236 _('Warning'), wx.YES_NO) == wx.YES): 237 self.iocontroller.purgeDeletedItems()
238
239 240 -class PrintPageSetup(settings_uicommand.SettingsCommand, 241 base_uicommand.UICommand):
242 ''' Action for changing page settings. The page settings are saved in the 243 application wide settings. ''' 244
245 - def __init__(self, *args, **kwargs):
246 super(PrintPageSetup, self).__init__( \ 247 menuText=_('&Page setup...\tShift+Ctrl+P'), 248 helpText=help.printPageSetup, bitmap='pagesetup', 249 id=wx.ID_PRINT_SETUP, *args, **kwargs)
250
251 - def doCommand(self, event):
252 printerSettings = printer.PrinterSettings(self.settings) 253 pageSetupDialog = wx.PageSetupDialog(self.mainWindow(), 254 printerSettings.pageSetupData) 255 result = pageSetupDialog.ShowModal() 256 if result == wx.ID_OK: 257 pageSetupData = pageSetupDialog.GetPageSetupData() 258 printerSettings.updatePageSetupData(pageSetupData) 259 pageSetupDialog.Destroy()
260
261 262 -class PrintPreview(ViewerCommand, settings_uicommand.SettingsCommand):
263 ''' Action for previewing a print of the current viewer. ''' 264
265 - def __init__(self, *args, **kwargs):
266 super(PrintPreview, self).__init__( \ 267 menuText=_('&Print preview...'), 268 helpText=_('Show a preview of what the print will look like'), 269 bitmap='printpreview', id=wx.ID_PREVIEW, *args, **kwargs)
270
271 - def doCommand(self, event):
272 printout, printout2 = printer.Printout(self.viewer, self.settings, 273 twoPrintouts=True) 274 printerSettings = printer.PrinterSettings(self.settings) 275 preview = wx.PrintPreview(printout, printout2, 276 printerSettings.printData) 277 previewFrame = wx.PreviewFrame(preview, self.mainWindow(), 278 _('Print preview'), size=(750, 700)) 279 previewFrame.Initialize() 280 previewFrame.Show()
281 306
307 308 -class FileExportCommand(IOCommand, settings_uicommand.SettingsCommand):
309 ''' Base class for export actions. ''' 310
311 - def doCommand(self, event):
312 exportDialog = self.getExportDialogClass()(self.mainWindow(), 313 settings=self.settings) # pylint: disable=E1101 314 if wx.ID_OK == exportDialog.ShowModal(): 315 exportOptions = exportDialog.options() 316 selectedViewer = exportOptions.pop('selectedViewer') 317 # pylint: disable=W0142 318 self.exportFunction()(selectedViewer, **exportOptions) 319 exportDialog.Destroy()
320 321 @staticmethod
322 - def getExportDialogClass():
323 ''' Return the class to be used for the export dialog. ''' 324 raise NotImplementedError
325
326 - def exportFunction(self):
327 ''' Return a function that does the actual export. The function should 328 take the selected viewer as the first parameter and possibly a 329 number of keyword arguments for export options. ''' 330 raise NotImplementedError # pragma: no cover
331
332 333 -class FileExportAsHTML(FileExportCommand):
334 ''' Action for exporting the contents of a viewer to HTML. ''' 335
336 - def __init__(self, *args, **kwargs):
337 super(FileExportAsHTML, self).__init__( \ 338 menuText=_('Export as &HTML...'), 339 helpText=_('Export items from a viewer in HTML format'), 340 bitmap='exportashtml', *args, **kwargs)
341 342 @staticmethod
343 - def getExportDialogClass():
345
346 - def exportFunction(self):
347 return self.iocontroller.exportAsHTML
348
349 350 -class FileExportAsCSV(FileExportCommand):
351 ''' Action for exporting the contents of a viewer to CSV. ''' 352
353 - def __init__(self, *args, **kwargs):
354 super(FileExportAsCSV, self).__init__(menuText=_('Export as &CSV...'), 355 helpText=_('Export items from a viewer in Comma Separated Values ' 356 '(CSV) format'), 357 bitmap='exportascsv', *args, **kwargs)
358 359 @staticmethod
360 - def getExportDialogClass():
362
363 - def exportFunction(self):
364 return self.iocontroller.exportAsCSV
365
366 367 -class FileExportAsICalendar(FileExportCommand):
368 ''' Action for exporting the contents of a viewer to iCalendar format. ''' 369
370 - def __init__(self, *args, **kwargs):
371 super(FileExportAsICalendar, self).__init__( \ 372 menuText=_('Export as &iCalendar...'), 373 helpText=_('Export items from a viewer in iCalendar format'), 374 bitmap='exportasvcal', *args, **kwargs)
375
376 - def exportFunction(self):
378
379 - def enabled(self, event):
380 return any(self.exportableViewer(viewer) for viewer in \ 381 self.mainWindow().viewer)
382 383 @staticmethod
384 - def getExportDialogClass():
386 387 @staticmethod
388 - def exportableViewer(aViewer):
389 ''' Return whether the viewer can be exported to iCalendar format. ''' 390 return aViewer.isShowingTasks() or (aViewer.isShowingEffort() and \ 391 not aViewer.isShowingAggregatedEffort())
392
393 394 -class FileExportAsTodoTxt(FileExportCommand):
395 ''' Action for exporting the contents of a viewer to Todo.txt format. ''' 396
397 - def __init__(self, *args, **kwargs):
398 super(FileExportAsTodoTxt, self).__init__( \ 399 menuText=_('Export as &Todo.txt...'), 400 helpText=_('Export items from a viewer in Todo.txt format ' 401 '(see todotxt.com)'), 402 bitmap='exportascsv', *args, **kwargs)
403
404 - def exportFunction(self):
405 return self.iocontroller.exportAsTodoTxt
406 - def enabled(self, event):
407 return any(self.exportableViewer(viewer) for viewer in \ 408 self.mainWindow().viewer)
409 410 @staticmethod
411 - def getExportDialogClass():
413 414 @staticmethod
415 - def exportableViewer(aViewer):
416 ''' Return whether the viewer can be exported to Todo.txt format. ''' 417 return aViewer.isShowingTasks()
418
419 420 -class FileExportAsPDF(IOCommand):
421 ''' Action for exporting the contents of a viewer to PDF. ''' 422 ''' author: Erik Ivarsson ''' 423
424 - def __init__(self, *args, **kwargs):
425 super(FileExportAsPDF, self).__init__( \ 426 menuText=_('Export as &PDF'), 427 helpText=_('Export items from a viewer in PDF format'), 428 bitmap='exportascsv', *args, **kwargs)
429 430 @staticmethod
431 - def getExportDialogClass():
433
434 - def exportFunction(self):
435 return self.iocontroller.exportAsPDF
436
437 -class FileExportToGoogleTask(IOCommand):
438 ''' Action for exporting the contents of a viewer to iCalendar format. ''' 439
440 - def __init__(self, *args, **kwargs):
441 super(FileExportToGoogleTask, self).__init__( \ 442 menuText=_('Export as &Google Task...'), 443 helpText=_('Export items from a viewer to Google Task'), 444 bitmap='exportascsv', *args, **kwargs)
445
446 - def doCommand(self,event):
448
449 450 451 452 -class FileImportCSV(IOCommand):
453 ''' Action for importing data from a CSV file into the current task 454 file. ''' 455
456 - def __init__(self, *args, **kwargs):
457 super(FileImportCSV, self).__init__(menuText=_('&Import CSV...'), 458 helpText=_('Import tasks from a Comma Separated Values (CSV) file'), 459 bitmap='exportascsv', *args, **kwargs)
460
461 - def doCommand(self, event):
462 while True: 463 filename = wx.FileSelector(_('Import CSV'), wildcard='*.csv') 464 if filename: 465 if len(file(filename, 'rb').read()) == 0: 466 wx.MessageBox(_('The selected file is empty. ' 467 'Please select a different file.'), 468 _('Import CSV')) 469 continue 470 wizard = CSVImportWizard(filename, None, wx.ID_ANY, 471 _('Import CSV')) 472 if wizard.RunWizard(): 473 self.iocontroller.importCSV(**wizard.GetOptions()) 474 break 475 else: 476 break
477
478 479 -class FileImportTodoTxt(IOCommand):
480 ''' Action for importing data from a Todo.txt file into the current task 481 file. ''' 482
483 - def __init__(self, *args, **kwargs):
484 super(FileImportTodoTxt, self).__init__( \ 485 menuText=_('&Import Todo.txt...'), 486 helpText=_('Import tasks from a Todo.txt (see todotxt.com) file'), 487 bitmap='exportascsv', *args, **kwargs)
488
489 - def doCommand(self, event):
490 filename = wx.FileSelector(_('Import Todo.txt'), wildcard='*.txt') 491 if filename: 492 self.iocontroller.importTodoTxt(filename)
493
494 495 -class FileImportFromGoogleTask(IOCommand):
496 - def __init__(self, *args, **kwargs):
497 super(FileImportFromGoogleTask, self).__init__( \ 498 menuText=_('&Import Google Task...'), 499 helpText=_('Import Tasks from Google Task'), 500 bitmap='exportascsv', *args, **kwargs)
501
502 - def doCommand(self, event):
503 tasklist=self.iocontroller.importFromGoogleTasks() 504 for task in tasklist: 505 506 existingTasks = self.mainWindow().viewer.taskFile.tasks() 507 exists = False 508 509 for existingTask in existingTasks: 510 if existingTask.id()==task['id'] or (existingTask.subject() == task['title'] and 511 existingTask.dueDateTime().strftime("%Y-%m-%dT00:00:00.000Z")==task['due']): 512 exists=True 513 break 514 515 if not exists: 516 category = self.mainWindow().taskFile.categories().findCategoryByName(task['Category']) 517 if category is None: 518 newCategoryCommand = command.NewCategoryCommand(self.mainWindow().taskFile.categories(), 519 subject=task['Category']) 520 newCategoryCommand.do() 521 category = self.mainWindow().taskFile.categories().findCategoryByName(task['Category']) 522 523 print task 524 newTaskCommand = command.NewTaskCommand(self.mainWindow().taskFile.tasks(), 525 subject=task['title'] if 'title' in task else '', 526 description=task['notes'] if 'notes' in task else '', 527 dueDateTime=date.DateTime.strptime 528 (task['due'],'%Y-%m-%dT%H:%M:%S.%fz') if 'due' in task 529 else date.DateTime.strptime 530 ('2010-10-15T12:00:00.000Z','%Y-%m-%dT%H:%M:%S.%fz'), 531 percentageComplete=100 if task['status']=='completed' else 0, 532 categories=[category],id=task['id']) 533 534 newTaskCommand.do()
535
536 537 -class FileBackupGoogleDrive(IOCommand):
538 - def __init__(self, *args, **kwargs):
539 super(FileBackupGoogleDrive, self).__init__( \ 540 menuText=_('&Backup to Google Drive'), 541 helpText=_('Backup your Taskfile to Google Drive'), 542 bitmap='', *args, **kwargs)
543
544 - def doCommand(self, event):
545 if self.mainWindow().viewer.taskFile.__str__() != "": 546 self.iocontroller.uploadToGoogleDrive(self.mainWindow().viewer.taskFile.__str__()) 547 wx.MessageBox("Backup completed",'Backup completed',wx.OK|wx.ICON_INFORMATION) 548 else: 549 wx.MessageBox('Please save task before doing a backup','Backup error',wx.OK|wx.ICON_ERROR)
550
551 552 -class FileSynchronize(IOCommand, settings_uicommand.SettingsCommand):
553 ''' Action for synchronizing the current task file with a SyncML 554 server. ''' 555
556 - def __init__(self, *args, **kwargs):
557 super(FileSynchronize, self).__init__( \ 558 menuText=_('S&yncML synchronization...'), 559 helpText=_('Synchronize with a SyncML server'), 560 bitmap='arrows_looped_icon', *args, **kwargs)
561
562 - def doCommand(self, event):
564
565 566 -class FileQuit(base_uicommand.UICommand):
567 ''' Action for quitting the application. ''' 568
569 - def __init__(self, *args, **kwargs):
570 super(FileQuit, self).__init__(menuText=_('&Quit\tCtrl+Q'), 571 helpText=help.fileQuit, bitmap='exit', id=wx.ID_EXIT, 572 *args, **kwargs)
573
574 - def doCommand(self, event):
575 self.mainWindow().Close(force=True)
576
577 578 -class EditUndo(base_uicommand.UICommand):
579 ''' Action for undoing the previous user action. ''' 580
581 - def __init__(self, *args, **kwargs):
582 super(EditUndo, self).__init__(menuText=self.getUndoMenuText(), 583 helpText=help.editUndo, bitmap='undo', id=wx.ID_UNDO, 584 *args, **kwargs)
585 586 @staticmethod
587 - def getUndoMenuText():
588 ''' Return the menu text for the undo command, including a text 589 describing the previous user action. ''' 590 return '%s\tCtrl+Z' % patterns.CommandHistory().undostr(_('&Undo'))
591
592 - def doCommand(self, event):
593 windowWithFocus = wx.Window.FindFocus() 594 if isinstance(windowWithFocus, wx.TextCtrl): 595 windowWithFocus.Undo() 596 else: 597 patterns.CommandHistory().undo()
598
599 - def onUpdateUI(self, event):
600 self.updateMenuText(self.getUndoMenuText()) 601 super(EditUndo, self).onUpdateUI(event)
602
603 - def enabled(self, event):
604 windowWithFocus = wx.Window.FindFocus() 605 if isinstance(windowWithFocus, wx.TextCtrl): 606 return windowWithFocus.CanUndo() 607 else: 608 return patterns.CommandHistory().hasHistory() and \ 609 super(EditUndo, self).enabled(event)
610
611 612 -class EditRedo(base_uicommand.UICommand):
613 ''' Action for redoing the last undone user action. ''' 614
615 - def __init__(self, *args, **kwargs):
616 super(EditRedo, self).__init__(menuText=self.getRedoMenuText(), 617 helpText=help.editRedo, bitmap='redo', id=wx.ID_REDO, 618 *args, **kwargs)
619 620 @staticmethod
621 - def getRedoMenuText():
622 ''' Return the menu text for the redo command, including a text 623 describing the next user action. ''' 624 return '%s\tCtrl+Y' % patterns.CommandHistory().redostr(_('&Redo'))
625
626 - def doCommand(self, event):
627 windowWithFocus = wx.Window.FindFocus() 628 if isinstance(windowWithFocus, wx.TextCtrl): 629 windowWithFocus.Redo() 630 else: 631 patterns.CommandHistory().redo()
632
633 - def onUpdateUI(self, event):
634 self.updateMenuText(self.getRedoMenuText()) 635 super(EditRedo, self).onUpdateUI(event)
636
637 - def enabled(self, event):
638 windowWithFocus = wx.Window.FindFocus() 639 if isinstance(windowWithFocus, wx.TextCtrl): 640 return windowWithFocus.CanRedo() 641 else: 642 return patterns.CommandHistory().hasFuture() and \ 643 super(EditRedo, self).enabled(event)
644
645 646 -class EditCut(mixin_uicommand.NeedsSelectionMixin, ViewerCommand):
647 ''' Action for cutting the currently selected item(s) to the 648 clipboard. ''' 649
650 - def __init__(self, *args, **kwargs):
651 super(EditCut, self).__init__(menuText=_('Cu&t\tCtrl+X'), 652 helpText=help.editCut, bitmap='cut', *args, **kwargs)
653
654 - def doCommand(self, event):
655 windowWithFocus = wx.Window.FindFocus() 656 if isinstance(windowWithFocus, wx.TextCtrl): 657 windowWithFocus.Cut() 658 else: 659 cutCommand = self.viewer.cutItemCommand() 660 cutCommand.do()
661
662 - def enabled(self, event):
663 windowWithFocus = wx.Window.FindFocus() 664 if isinstance(windowWithFocus, wx.TextCtrl): 665 return windowWithFocus.CanCut() 666 else: 667 return super(EditCut, self).enabled(event)
668
669 670 -class EditCopy(mixin_uicommand.NeedsSelectionMixin, ViewerCommand):
671 ''' Action for copying the currently selected item(s) to the 672 clipboard. ''' 673
674 - def __init__(self, *args, **kwargs):
675 super(EditCopy, self).__init__(menuText=_('&Copy\tCtrl+C'), 676 helpText=help.editCopy, bitmap='copy', *args, **kwargs)
677
678 - def doCommand(self, event):
679 windowWithFocus = wx.Window.FindFocus() 680 if isinstance(windowWithFocus, wx.TextCtrl): 681 windowWithFocus.Copy() 682 else: 683 copyCommand = command.CopyCommand(self.viewer.presentation(), 684 self.viewer.curselection()) 685 copyCommand.do()
686
687 - def enabled(self, event):
688 windowWithFocus = wx.Window.FindFocus() 689 if isinstance(windowWithFocus, wx.TextCtrl): 690 return windowWithFocus.CanCopy() 691 else: 692 return super(EditCopy, self).enabled(event)
693
694 695 -class EditPaste(base_uicommand.UICommand):
696 ''' Action for pasting the item(s) in the clipboard into the current 697 taskfile. ''' 698
699 - def __init__(self, *args, **kwargs):
700 super(EditPaste, self).__init__(menuText=_('&Paste\tCtrl+V'), 701 helpText=help.editPaste, bitmap='paste', id=wx.ID_PASTE, 702 *args, **kwargs)
703
704 - def doCommand(self, event):
705 windowWithFocus = wx.Window.FindFocus() 706 if isinstance(windowWithFocus, wx.TextCtrl): 707 windowWithFocus.Paste() 708 else: 709 pasteCommand = command.PasteCommand() 710 pasteCommand.do()
711
712 - def enabled(self, event):
713 windowWithFocus = wx.Window.FindFocus() 714 if isinstance(windowWithFocus, wx.TextCtrl): 715 return windowWithFocus.CanPaste() 716 else: 717 return command.Clipboard() and super(EditPaste, self).enabled(event)
718
719 720 -class EditPasteAsSubItem(mixin_uicommand.NeedsSelectedCompositeMixin, 721 ViewerCommand):
722 ''' Action for pasting the item(s) in the clipboard into the current 723 taskfile, as a subitem of the currently selected item. ''' 724
725 - def __init__(self, *args, **kwargs):
726 super(EditPasteAsSubItem, self).__init__( 727 menuText=_('P&aste as subitem\tShift+Ctrl+V'), 728 helpText=help.editPasteAsSubitem, bitmap='pasteintotask', 729 *args, **kwargs)
730
731 - def doCommand(self, event):
732 pasteCommand = command.PasteAsSubItemCommand( 733 items=self.viewer.curselection()) 734 pasteCommand.do()
735
736 - def enabled(self, event):
737 if not (super(EditPasteAsSubItem, self).enabled(event) and \ 738 command.Clipboard()): 739 return False 740 targetClass = self.viewer.curselection()[0].__class__ 741 pastedClasses = [item.__class__ for item in command.Clipboard().peek()] 742 return self.__targetAndPastedAreEqual(targetClass, pastedClasses) or \ 743 self.__targetIsTaskAndPastedIsEffort(targetClass, pastedClasses)
744 745 @classmethod
746 - def __targetIsTaskAndPastedIsEffort(cls, targetClass, pastedClasses):
747 ''' Return whether the target class is a task and the pasted classes 748 are all effort. ''' 749 if targetClass != task.Task: 750 return False 751 return cls.__targetAndPastedAreEqual(effort.Effort, pastedClasses)
752 753 @staticmethod
754 - def __targetAndPastedAreEqual(targetClass, pastedClasses):
755 ''' Return whether the targetClass and pastedClasses are all equal. ''' 756 for pastedClass in pastedClasses: 757 if pastedClass != targetClass: 758 return False 759 return True
760
761 762 -class EditPreferences(settings_uicommand.SettingsCommand):
763 ''' Action for bringing up the preferences dialog. ''' 764
765 - def __init__(self, *args, **kwargs):
766 super(EditPreferences, self).__init__( \ 767 menuText=_('&Preferences...\tAlt+P'), 768 helpText=help.editPreferences, bitmap='wrench_icon', 769 id=wx.ID_PREFERENCES, *args, **kwargs)
770
771 - def doCommand(self, event, show=True): # pylint: disable=W0221
772 editor = dialog.preferences.Preferences(parent=self.mainWindow(), 773 title=_('Preferences'), settings=self.settings) 774 editor.Show(show=show)
775
776 777 -class EditSyncPreferences(IOCommand):
778 ''' Action for bringing up the synchronization preferences dialog. ''' 779
780 - def __init__(self, *args, **kwargs):
781 super(EditSyncPreferences, self).__init__( \ 782 menuText=_('&SyncML preferences...'), 783 helpText=_('Edit SyncML preferences'), bitmap='arrows_looped_icon', 784 *args, **kwargs)
785
786 - def doCommand(self, event, show=True): # pylint: disable=W0221
787 editor = dialog.syncpreferences.SyncMLPreferences( \ 788 parent=self.mainWindow(), iocontroller=self.iocontroller, 789 title=_('SyncML preferences')) 790 editor.Show(show=show)
791
792 793 -class EditToolBarPerspective(settings_uicommand.SettingsCommand):
794 ''' Action for editing a customizable toolbar ''' 795
796 - def __init__(self, toolbar, editorClass, *args, **kwargs):
797 self.__toolbar = toolbar 798 self.__editorClass = editorClass 799 super(EditToolBarPerspective, self).__init__( \ 800 helpText=_('Customize toolbar'), bitmap='cogwheel_icon', 801 menuText=_('Customize'), 802 *args, **kwargs)
803
804 - def doCommand(self, event):
805 self.__editorClass(self.__toolbar, self.settings, self.mainWindow(), _('Customize toolbar')).ShowModal()
806
807 808 -class SelectAll(mixin_uicommand.NeedsItemsMixin, ViewerCommand):
809 ''' Action for selecting all items in a viewer. ''' 810
811 - def __init__(self, *args, **kwargs):
812 super(SelectAll, self).__init__(menuText=_('&All\tCtrl+A'), 813 helpText=help.editSelectAll, bitmap='selectall', 814 id=wx.ID_SELECTALL, *args, **kwargs)
815
816 - def doCommand(self, event):
817 windowWithFocus = wx.Window.FindFocus() 818 if self.windowIsTextCtrl(windowWithFocus): 819 windowWithFocus.SetSelection(-1, -1) # Select all text 820 else: 821 self.viewer.select_all()
822 823 @staticmethod
824 - def windowIsTextCtrl(window):
825 ''' Return whether the window is a text control. ''' 826 return isinstance(window, wx.TextCtrl) or \ 827 isinstance(window, hypertreelist.EditCtrl)
828
829 830 -class ClearSelection(mixin_uicommand.NeedsSelectionMixin, ViewerCommand):
831 ''' Action for unselecting all items in a viewer. ''' 832
833 - def __init__(self, *args, **kwargs):
834 super(ClearSelection, self).__init__(menuText=_('&Clear selection'), 835 helpText=_('Unselect all items'), *args, **kwargs)
836
837 - def doCommand(self, event):
838 self.viewer.clear_selection()
839
840 841 -class ResetFilter(ViewerCommand):
842 ''' Action for resetting all filters so that all items in all viewers 843 become visible. ''' 844
845 - def __init__(self, *args, **kwargs):
846 super(ResetFilter, self).__init__( \ 847 menuText=_('&Clear all filters\tShift-Ctrl-R'), 848 helpText=help.resetFilter, bitmap='viewalltasks', *args, **kwargs)
849
850 - def doCommand(self, event):
851 self.viewer.resetFilter()
852
853 - def enabled(self, event):
854 return self.viewer.hasFilter()
855
856 857 -class ResetCategoryFilter(mixin_uicommand.NeedsAtLeastOneCategoryMixin, 858 CategoriesCommand):
859 ''' Action for resetting all category filters so that items are no longer 860 hidden if the don't belong to a certain category. ''' 861
862 - def __init__(self, *args, **kwargs):
863 super(ResetCategoryFilter, self).__init__( \ 864 menuText=_('&Reset all categories\tCtrl-R'), 865 helpText=help.resetCategoryFilter, *args, **kwargs)
866
867 - def doCommand(self, event):
869
870 871 -class ToggleCategoryFilter(base_uicommand.UICommand):
872 ''' Action for toggling filtering on a specific category. ''' 873
874 - def __init__(self, *args, **kwargs):
875 self.category = kwargs.pop('category') 876 subject = self.category.subject() 877 # Would like to use wx.ITEM_RADIO for mutually exclusive categories, but 878 # a menu with radio items always has to have at least of the items 879 # checked, while we allow none of the mutually exclusive categories to 880 # be checked. Dynamically changing between wx.ITEM_CHECK and 881 # wx.ITEM_RADIO would be a work-around in theory, using wx.ITEM_CHECK 882 # when none of the mutually exclusive categories is checked and 883 # wx.ITEM_RADIO otherwise, but dynamically changing the type of menu 884 # items isn't possible. Hence, we use wx.ITEM_CHECK, even for mutual 885 # exclusive categories. 886 kind = wx.ITEM_CHECK 887 super(ToggleCategoryFilter, self).__init__( \ 888 menuText='&' + subject.replace('&', '&&'), 889 helpText=_('Show/hide items belonging to %s') % subject, kind=kind, 890 *args, **kwargs)
891
892 - def doCommand(self, event):
893 self.category.setFiltered(event.IsChecked())
894
895 896 -class ViewViewer(settings_uicommand.SettingsCommand, ViewerCommand):
897 ''' Action for opening a new viewer of a specific class. ''' 898
899 - def __init__(self, *args, **kwargs):
900 self.taskFile = kwargs.pop('taskFile') 901 self.viewerClass = kwargs.pop('viewerClass') 902 kwargs.setdefault('bitmap', self.viewerClass.defaultBitmap) 903 super(ViewViewer, self).__init__(*args, **kwargs)
904
905 - def doCommand(self, event):
906 from taskcoachlib.gui import viewer 907 908 viewer.addOneViewer(self.viewer, self.taskFile, self.settings, 909 self.viewerClass) 910 self.increaseViewerCount()
911
912 - def increaseViewerCount(self):
913 ''' Increase the viewer count for the viewer class this command is 914 opening and store the viewer count in the settings. ''' 915 setting = self.viewerClass.__name__.lower() + 'count' 916 viewerCount = self.settings.getint('view', setting) 917 self.settings.set('view', setting, str(viewerCount + 1))
918
919 920 -class ViewEffortViewerForSelectedTask(mixin_uicommand.NeedsOneSelectedTaskMixin, 921 settings_uicommand.SettingsCommand, 922 ViewerCommand):
923 - def __init__(self, *args, **kwargs):
924 from taskcoachlib.gui import viewer 925 926 self.viewerClass = viewer.EffortViewer 927 self.taskFile = kwargs.pop('taskFile') 928 kwargs['bitmap'] = viewer.EffortViewer.defaultBitmap 929 super(ViewEffortViewerForSelectedTask, self).__init__(*args, **kwargs)
930
931 - def doCommand(self, event):
932 from taskcoachlib.gui import viewer 933 934 viewer.addOneViewer(self.viewer, self.taskFile, self.settings, 935 self.viewerClass, 936 tasksToShowEffortFor=task.TaskList(self.viewer.curselection()))
937
938 939 -class RenameViewer(ViewerCommand):
940 - def __init__(self, *args, **kwargs):
941 super(RenameViewer, self).__init__(menuText=_('&Rename viewer...'), 942 helpText=_('Rename the selected viewer'), *args, **kwargs)
943
944 - def doCommand(self, event):
945 activeViewer = self.viewer.activeViewer() 946 viewerNameDialog = wx.TextEntryDialog(self.mainWindow(), 947 _('New title for the viewer:'), _('Rename viewer'), 948 activeViewer.title()) 949 if viewerNameDialog.ShowModal() == wx.ID_OK: 950 activeViewer.setTitle(viewerNameDialog.GetValue()) 951 viewerNameDialog.Destroy()
952
953 - def enabled(self, event):
954 return bool(self.viewer.activeViewer())
955
956 957 -class ActivateViewer(ViewerCommand):
958 - def __init__(self, *args, **kwargs):
959 self.direction = kwargs.pop('forward') 960 super(ActivateViewer, self).__init__(*args, **kwargs)
961
962 - def doCommand(self, event):
963 self.viewer.containerWidget.advanceSelection(self.direction)
964
965 - def enabled(self, event):
966 return self.viewer.containerWidget.viewerCount() > 1
967
968 969 -class HideCurrentColumn(ViewerCommand):
970 - def __init__(self, *args, **kwargs):
971 super(HideCurrentColumn, self).__init__(menuText=_('&Hide this column'), 972 helpText=_('Hide the selected column'), *args, **kwargs)
973
974 - def doCommand(self, event):
975 columnPopupMenu = event.GetEventObject() 976 self.viewer.hideColumn(columnPopupMenu.columnIndex)
977
978 - def enabled(self, event):
979 # Unfortunately the event (an UpdateUIEvent) does not give us any 980 # information to determine the current column, so we have to find 981 # the column ourselves. We use the current mouse position to do so. 982 widget = self.viewer.getWidget() # Must use method to make sure viewer dispatch works! 983 x, y = widget.ScreenToClient(wx.GetMousePosition()) 984 # Use wx.Point because CustomTreeCtrl assumes a wx.Point instance: 985 columnIndex = widget.HitTest(wx.Point(x, y))[2] 986 # The TreeListCtrl returns -1 for the first column sometimes, 987 # don't understand why. Work around as follows: 988 if columnIndex == -1: 989 columnIndex = 0 990 return self.viewer.isHideableColumn(columnIndex)
991
992 993 -class ViewColumn(ViewerCommand, settings_uicommand.UICheckCommand):
994 - def isSettingChecked(self):
995 return self.viewer.isVisibleColumnByName(self.setting)
996
997 - def doCommand(self, event):
998 self.viewer.showColumnByName(self.setting, 999 self._isMenuItemChecked(event))
1000
1001 1002 -class ViewColumns(ViewerCommand, settings_uicommand.UICheckCommand):
1003 - def isSettingChecked(self):
1004 for columnName in self.setting: 1005 if not self.viewer.isVisibleColumnByName(columnName): 1006 return False 1007 return True
1008
1009 - def doCommand(self, event):
1010 show = self._isMenuItemChecked(event) 1011 for columnName in self.setting: 1012 self.viewer.showColumnByName(columnName, show)
1013
1014 1015 -class ViewExpandAll(mixin_uicommand.NeedsTreeViewerMixin, ViewerCommand):
1016 - def __init__(self, *args, **kwargs):
1017 super(ViewExpandAll, self).__init__( \ 1018 menuText=_('&Expand all items\tShift+Ctrl+E'), 1019 helpText=help.viewExpandAll, *args, **kwargs)
1020
1021 - def enabled(self, event):
1022 return super(ViewExpandAll, self).enabled(event) and \ 1023 self.viewer.isAnyItemExpandable()
1024
1025 - def doCommand(self, event):
1026 self.viewer.expandAll()
1027
1028 1029 -class ViewCollapseAll(mixin_uicommand.NeedsTreeViewerMixin, ViewerCommand):
1030 - def __init__(self, *args, **kwargs):
1031 super(ViewCollapseAll, self).__init__( \ 1032 menuText=_('&Collapse all items\tShift+Ctrl+C'), 1033 helpText=help.viewCollapseAll, *args, **kwargs)
1034
1035 - def enabled(self, event):
1036 return super(ViewCollapseAll, self).enabled(event) and \ 1037 self.viewer.isAnyItemCollapsable()
1038
1039 - def doCommand(self, event):
1040 self.viewer.collapseAll()
1041
1042 1043 -class ViewerSortByCommand(ViewerCommand, settings_uicommand.UIRadioCommand):
1044 - def isSettingChecked(self):
1045 return self.viewer.isSortedBy(self.value)
1046
1047 - def doCommand(self, event):
1048 self.viewer.sortBy(self.value)
1049
1050 1051 -class ViewerSortOrderCommand(ViewerCommand, settings_uicommand.UICheckCommand):
1052 - def __init__(self, *args, **kwargs):
1053 super(ViewerSortOrderCommand, self).__init__(menuText=_('&Ascending'), 1054 helpText=_('Sort ascending (checked) or descending (unchecked)'), 1055 *args, **kwargs)
1056
1057 - def isSettingChecked(self):
1058 return self.viewer.isSortOrderAscending()
1059
1060 - def doCommand(self, event):
1062
1063 1064 -class ViewerSortCaseSensitive(ViewerCommand, settings_uicommand.UICheckCommand):
1065 - def __init__(self, *args, **kwargs):
1066 super(ViewerSortCaseSensitive, self).__init__( \ 1067 menuText=_('Sort &case sensitive'), 1068 helpText=_('When comparing text, sorting is case sensitive ' 1069 '(checked) or insensitive (unchecked)'), 1070 *args, **kwargs)
1071
1072 - def isSettingChecked(self):
1073 return self.viewer.isSortCaseSensitive()
1074
1075 - def doCommand(self, event):
1077
1078 1079 -class ViewerSortByTaskStatusFirst(ViewerCommand, 1080 settings_uicommand.UICheckCommand):
1081 - def __init__(self, *args, **kwargs):
1082 super(ViewerSortByTaskStatusFirst, self).__init__( \ 1083 menuText=_('Sort by status &first'), 1084 helpText=_('Sort tasks by status (active/inactive/completed) ' 1085 'first'), 1086 *args, **kwargs)
1087
1088 - def isSettingChecked(self):
1089 return self.viewer.isSortByTaskStatusFirst()
1090
1091 - def doCommand(self, event):
1093
1094 1095 -class ViewerHideTasks(ViewerCommand, settings_uicommand.UICheckCommand):
1096 - def __init__(self, taskStatus, *args, **kwargs):
1097 self.__taskStatus = taskStatus 1098 super(ViewerHideTasks, self).__init__(menuText=taskStatus.hideMenuText, 1099 helpText=taskStatus.hideHelpText, 1100 bitmap=taskStatus.getHideBitmap(kwargs['settings']), 1101 *args, **kwargs)
1102
1103 - def uniqueName(self):
1104 return super(ViewerHideTasks, self).uniqueName() + '_' + unicode(self.__taskStatus)
1105
1106 - def isSettingChecked(self):
1107 return self.viewer.isHidingTaskStatus(self.__taskStatus)
1108
1109 - def doCommand(self, event):
1110 if wx.GetKeyState(wx.WXK_SHIFT): 1111 self.viewer.showOnlyTaskStatus(self.__taskStatus) 1112 else: 1113 self.viewer.hideTaskStatus(self.__taskStatus, 1114 self._isMenuItemChecked(event))
1115
1116 1117 -class ViewerHideCompositeTasks(ViewerCommand, 1118 settings_uicommand.UICheckCommand):
1119 - def __init__(self, *args, **kwargs):
1120 super(ViewerHideCompositeTasks, self).__init__( \ 1121 menuText=_('Hide c&omposite tasks'), 1122 helpText=_('Show/hide tasks with subtasks in list mode'), 1123 *args, **kwargs)
1124
1125 - def isSettingChecked(self):
1126 return self.viewer.isHidingCompositeTasks()
1127
1128 - def doCommand(self, event):
1130
1131 - def enabled(self, event):
1132 return not self.viewer.isTreeViewer()
1133
1134 1135 -class Edit(mixin_uicommand.NeedsSelectionMixin, ViewerCommand):
1136 - def __init__(self, *args, **kwargs):
1137 super(Edit, self).__init__(menuText=_('&Edit...\tRETURN'), 1138 helpText=_('Edit the selected item(s)'), bitmap='edit', 1139 *args, **kwargs)
1140
1141 - def doCommand(self, event, show=True): # pylint: disable=W0221
1142 windowWithFocus = wx.Window.FindFocus() 1143 editCtrl = self.findEditCtrl(windowWithFocus) 1144 if editCtrl: 1145 editCtrl.AcceptChanges() 1146 if editCtrl: 1147 editCtrl.Finish() 1148 return 1149 try: 1150 columnName = event.columnName 1151 except AttributeError: 1152 columnName = '' 1153 editor = self.viewer.editItemDialog(self.viewer.curselection(), 1154 self.bitmap, columnName) 1155 editor.Show(show)
1156
1157 - def enabled(self, event):
1158 windowWithFocus = wx.Window.FindFocus() 1159 if self.findEditCtrl(windowWithFocus): 1160 return True 1161 elif operating_system.isMac() and isinstance(windowWithFocus, 1162 wx.TextCtrl): 1163 return False 1164 else: 1165 return super(Edit, self).enabled(event)
1166
1167 - def findEditCtrl(self, windowWithFocus):
1168 while windowWithFocus: 1169 if isinstance(windowWithFocus, thirdparty.hypertreelist.EditCtrl): 1170 break 1171 windowWithFocus = windowWithFocus.GetParent() 1172 return windowWithFocus
1173
1174 1175 -class EditTrackedTasks(TaskListCommand, settings_uicommand.SettingsCommand):
1176 - def __init__(self, *args, **kwargs):
1177 super(EditTrackedTasks, self).__init__( \ 1178 menuText=_('Edit &tracked task...\tShift-Alt-T'), 1179 helpText=_('Edit the currently tracked task(s)'), bitmap='edit', 1180 *args, **kwargs)
1181
1182 - def doCommand(self, event, show=True):
1183 editTaskDialog = dialog.editor.TaskEditor(self.mainWindow(), 1184 self.taskList.tasksBeingTracked(), self.settings, self.taskList, 1185 self.mainWindow().taskFile, bitmap=self.bitmap) 1186 editTaskDialog.Show(show) 1187 return editTaskDialog # for testing purposes
1188
1189 - def enabled(self, event):
1190 return any(self.taskList.tasksBeingTracked())
1191
1192 1193 -class Delete(mixin_uicommand.NeedsSelectionMixin, ViewerCommand):
1194 - def __init__(self, *args, **kwargs):
1195 super(Delete, self).__init__(menuText=_('&Delete\tDEL'), 1196 helpText=_('Delete the selected item(s)'), bitmap='delete', 1197 *args, **kwargs)
1198
1199 - def doCommand(self, event):
1200 windowWithFocus = wx.Window.FindFocus() 1201 if self.windowIsTextCtrl(windowWithFocus): 1202 # Simulate Delete key press 1203 fromIndex, toIndex = windowWithFocus.GetSelection() 1204 if fromIndex == toIndex: 1205 pos = windowWithFocus.GetInsertionPoint() 1206 fromIndex, toIndex = pos, pos + 1 1207 windowWithFocus.Remove(fromIndex, toIndex) 1208 else: 1209 deleteCommand = self.viewer.deleteItemCommand() 1210 deleteCommand.do()
1211
1212 - def enabled(self, event):
1213 windowWithFocus = wx.Window.FindFocus() 1214 if self.windowIsTextCtrl(windowWithFocus): 1215 return True 1216 else: 1217 return super(Delete, self).enabled(event)
1218 1219 @staticmethod
1220 - def windowIsTextCtrl(window):
1221 return isinstance(window, wx.TextCtrl) or \ 1222 isinstance(window, hypertreelist.EditCtrl)
1223
1224 1225 -class TaskNew(TaskListCommand, settings_uicommand.SettingsCommand):
1226 - def __init__(self, *args, **kwargs):
1227 self.taskKeywords = kwargs.pop('taskKeywords', dict()) 1228 taskList = kwargs['taskList'] 1229 if 'menuText' not in kwargs: # Provide for subclassing 1230 kwargs['menuText'] = taskList.newItemMenuText 1231 kwargs['helpText'] = taskList.newItemHelpText 1232 super(TaskNew, self).__init__(bitmap='new', *args, **kwargs)
1233
1234 - def doCommand(self, event, show=True): # pylint: disable=W0221
1235 kwargs = self.taskKeywords.copy() 1236 if self.__shouldPresetPlannedStartDateTime(): 1237 kwargs['plannedStartDateTime'] = task.Task.suggestedPlannedStartDateTime() 1238 if self.__shouldPresetDueDateTime(): 1239 kwargs['dueDateTime'] = task.Task.suggestedDueDateTime() 1240 if self.__shouldPresetActualStartDateTime(): 1241 kwargs['actualStartDateTime'] = task.Task.suggestedActualStartDateTime() 1242 if self.__shouldPresetCompletionDateTime(): 1243 kwargs['completionDateTime'] = task.Task.suggestedCompletionDateTime() 1244 if self.__shouldPresetReminderDateTime(): 1245 kwargs['reminder'] = task.Task.suggestedReminderDateTime() 1246 newTaskCommand = command.NewTaskCommand(self.taskList, 1247 categories=self.categoriesForTheNewTask(), 1248 prerequisites=self.prerequisitesForTheNewTask(), 1249 dependencies=self.dependenciesForTheNewTask(), 1250 **kwargs) 1251 newTaskCommand.do() 1252 newTaskDialog = dialog.editor.TaskEditor(self.mainWindow(), 1253 newTaskCommand.items, self.settings, self.taskList, 1254 self.mainWindow().taskFile, bitmap=self.bitmap, items_are_new=True) 1255 newTaskDialog.Show(show) 1256 return newTaskDialog # for testing purposes
1257
1258 - def categoriesForTheNewTask(self):
1259 return self.mainWindow().taskFile.categories().filteredCategories()
1260
1261 - def prerequisitesForTheNewTask(self):
1262 return []
1263
1264 - def dependenciesForTheNewTask(self):
1265 return []
1266
1267 - def __shouldPresetPlannedStartDateTime(self):
1268 return 'plannedStartDateTime' not in self.taskKeywords and \ 1269 self.settings.get('view', 'defaultplannedstartdatetime').startswith('preset')
1270
1271 - def __shouldPresetDueDateTime(self):
1272 return 'dueDateTime' not in self.taskKeywords and \ 1273 self.settings.get('view', 'defaultduedatetime').startswith('preset')
1274
1275 - def __shouldPresetActualStartDateTime(self):
1276 return 'actualStartDateTime' not in self.taskKeywords and \ 1277 self.settings.get('view', 'defaultactualstartdatetime').startswith('preset')
1278
1279 - def __shouldPresetCompletionDateTime(self):
1280 return 'completionDateTime' not in self.taskKeywords and \ 1281 self.settings.get('view', 'defaultcompletiondatetime').startswith('preset')
1282
1283 - def __shouldPresetReminderDateTime(self):
1284 return 'reminder' not in self.taskKeywords and \ 1285 self.settings.get('view', 'defaultreminderdatetime').startswith('preset')
1286
1287 1288 -class TaskNewFromTemplate(TaskNew):
1289 - def __init__(self, filename, *args, **kwargs):
1290 super(TaskNewFromTemplate, self).__init__(*args, **kwargs) 1291 self.__filename = filename 1292 templateTask = self.__readTemplate() 1293 self.menuText = '&' + templateTask.subject().replace('&', '&&') # pylint: disable=E1103
1294
1295 - def __readTemplate(self):
1296 return persistence.TemplateXMLReader(file(self.__filename, 1297 'rU')).read()
1298
1299 - def doCommand(self, event, show=True): # pylint: disable=W0221
1300 # The task template is read every time because it's the 1301 # TemplateXMLReader that evaluates dynamic values (Now() 1302 # should be evaluated at task creation for instance). 1303 templateTask = self.__readTemplate() 1304 kwargs = templateTask.__getcopystate__() # pylint: disable=E1103 1305 kwargs['categories'] = self.categoriesForTheNewTask() 1306 newTaskCommand = command.NewTaskCommand(self.taskList, **kwargs) 1307 newTaskCommand.do() 1308 # pylint: disable=W0142 1309 newTaskDialog = dialog.editor.TaskEditor(self.mainWindow(), 1310 newTaskCommand.items, self.settings, self.taskList, 1311 self.mainWindow().taskFile, bitmap=self.bitmap, items_are_new=True) 1312 newTaskDialog.Show(show) 1313 return newTaskDialog # for testing purposes
1314
1315 1316 -class TaskNewFromTemplateButton(mixin_uicommand.PopupButtonMixin, 1317 TaskListCommand, 1318 settings_uicommand.SettingsCommand):
1319 - def createPopupMenu(self):
1320 from taskcoachlib.gui import menu 1321 1322 return menu.TaskTemplateMenu(self.mainWindow(), self.taskList, 1323 self.settings)
1324
1325 - def getMenuText(self):
1326 return _('New from &template')
1327
1328 - def getHelpText(self):
1329 return _('Create a new task from a template')
1330
1331 1332 -class NewTaskWithSelectedCategories(TaskNew, ViewerCommand):
1333 - def __init__(self, *args, **kwargs):
1334 super(NewTaskWithSelectedCategories, self).__init__( \ 1335 menuText=_('New task with selected &categories...'), 1336 helpText=_('Insert a new task with the selected categories checked'), 1337 *args, **kwargs)
1338
1339 - def categoriesForTheNewTask(self):
1340 return self.viewer.curselection()
1341
1342 1343 -class NewTaskWithSelectedTasksAsPrerequisites( \ 1344 mixin_uicommand.NeedsSelectedTasksMixin, TaskNew, ViewerCommand):
1345 - def __init__(self, *args, **kwargs):
1346 super(NewTaskWithSelectedTasksAsPrerequisites, self).__init__( 1347 menuText=_('New task with selected tasks as &prerequisites...'), 1348 helpText=_('Insert a new task with the selected tasks as prerequisite tasks'), 1349 *args, **kwargs)
1350
1351 - def prerequisitesForTheNewTask(self):
1352 return self.viewer.curselection()
1353
1354 1355 -class NewTaskWithSelectedTasksAsDependencies( \ 1356 mixin_uicommand.NeedsSelectedTasksMixin, TaskNew, ViewerCommand):
1357 - def __init__(self, *args, **kwargs):
1358 super(NewTaskWithSelectedTasksAsDependencies, self).__init__( 1359 menuText=_('New task with selected tasks as &dependents...'), 1360 helpText=_('Insert a new task with the selected tasks as dependent tasks'), 1361 *args, **kwargs)
1362
1363 - def dependenciesForTheNewTask(self):
1364 return self.viewer.curselection()
1365
1366 1367 -class NewSubItem(mixin_uicommand.NeedsOneSelectedCompositeItemMixin, 1368 ViewerCommand):
1369 shortcut = ('\tCtrl+INS' if operating_system.isWindows() else '\tShift+Ctrl+N') 1370 defaultMenuText = _('New &subitem...') + shortcut 1371 labels = {task.Task: _('New &subtask...'), 1372 note.Note: _('New &subnote...'), 1373 category.Category: _('New &subcategory...')} 1374
1375 - def __init__(self, *args, **kwargs):
1376 super(NewSubItem, self).__init__(menuText=self.defaultMenuText, 1377 helpText=_('Insert a new subitem of the selected item'), 1378 bitmap='newsub', *args, **kwargs)
1379
1380 - def doCommand(self, event, show=True): # pylint: disable=W0221
1381 self.viewer.newSubItemDialog(bitmap=self.bitmap).Show(show)
1382
1383 - def onUpdateUI(self, event):
1384 super(NewSubItem, self).onUpdateUI(event) 1385 self.updateMenuText(self.__menuText())
1386
1387 - def __menuText(self):
1388 for class_ in self.labels: 1389 if self.viewer.curselectionIsInstanceOf(class_): 1390 return self.labels[class_] + self.shortcut 1391 return self.defaultMenuText
1392
1393 1394 -class TaskMarkActive(mixin_uicommand.NeedsSelectedTasksMixin, settings_uicommand.SettingsCommand, ViewerCommand):
1395 - def __init__(self, *args, **kwargs):
1396 super(TaskMarkActive, self).__init__(bitmap=task.active.getBitmap(kwargs['settings']), 1397 menuText=_('Mark task &active\tAlt+RETURN'), 1398 helpText=_('Mark the selected task(s) active'), 1399 *args, **kwargs)
1400
1401 - def doCommand(self, event):
1404
1405 - def enabled(self, event):
1406 def canBeMarkedActive(aTask): 1407 return aTask.actualStartDateTime() > date.Now() or aTask.completed()
1408 1409 return super(TaskMarkActive, self).enabled(event) and \ 1410 any([canBeMarkedActive(task) for task in self.viewer.curselection()])
1411
1412 1413 -class TaskMarkInactive(mixin_uicommand.NeedsSelectedTasksMixin, settings_uicommand.SettingsCommand, ViewerCommand):
1414 - def __init__(self, *args, **kwargs):
1415 super(TaskMarkInactive, self).__init__(bitmap=task.inactive.getBitmap(kwargs['settings']), 1416 menuText=_('Mark task &inactive\tCtrl+Alt+RETURN'), 1417 helpText=_('Mark the selected task(s) inactive'), 1418 *args, **kwargs)
1419
1420 - def doCommand(self, event):
1423
1424 - def enabled(self, event):
1425 def canBeMarkedInactive(aTask): 1426 return not aTask.inactive() and not aTask.late()
1427 1428 return super(TaskMarkInactive, self).enabled(event) and \ 1429 any([canBeMarkedInactive(task) for task in self.viewer.curselection()])
1430
1431 1432 -class TaskMarkCompleted(mixin_uicommand.NeedsSelectedTasksMixin, settings_uicommand.SettingsCommand, ViewerCommand):
1433 - def __init__(self, *args, **kwargs):
1434 super(TaskMarkCompleted, self).__init__(bitmap=task.completed.getBitmap(kwargs['settings']), 1435 menuText=_('Mark task &completed\tCtrl+RETURN'), 1436 helpText=_('Mark the selected task(s) completed'), 1437 *args, **kwargs)
1438
1439 - def doCommand(self, event):
1440 markCompletedCommand = command.MarkCompletedCommand( \ 1441 self.viewer.presentation(), self.viewer.curselection()) 1442 markCompletedCommand.do()
1443
1444 - def enabled(self, event):
1445 def canBeMarkedCompleted(task): 1446 return not task.completed()
1447 1448 return super(TaskMarkCompleted, self).enabled(event) and \ 1449 any([canBeMarkedCompleted(task) for task in self.viewer.curselection()])
1450
1451 1452 -class TaskMaxPriority(mixin_uicommand.NeedsSelectedTasksMixin, TaskListCommand, 1453 ViewerCommand):
1454 - def __init__(self, *args, **kwargs):
1455 super(TaskMaxPriority, self).__init__( 1456 menuText=_('&Maximize priority\tShift+Ctrl+I'), 1457 helpText=help.taskMaxPriority, bitmap='maxpriority', 1458 *args, **kwargs)
1459
1460 - def doCommand(self, event):
1461 maxPriority = command.MaxPriorityCommand(self.taskList, 1462 self.viewer.curselection()) 1463 maxPriority.do()
1464
1465 1466 -class TaskMinPriority(mixin_uicommand.NeedsSelectedTasksMixin, TaskListCommand, 1467 ViewerCommand):
1468 - def __init__(self, *args, **kwargs):
1469 super(TaskMinPriority, self).__init__( 1470 menuText=_('&Minimize priority\tShift+Ctrl+D'), 1471 helpText=help.taskMinPriority, bitmap='minpriority', 1472 *args, **kwargs)
1473
1474 - def doCommand(self, event):
1475 minPriority = command.MinPriorityCommand(self.taskList, 1476 self.viewer.curselection()) 1477 minPriority.do()
1478
1479 1480 -class TaskIncPriority(mixin_uicommand.NeedsSelectedTasksMixin, TaskListCommand, 1481 ViewerCommand):
1482 - def __init__(self, *args, **kwargs):
1483 super(TaskIncPriority, self).__init__( 1484 menuText=_('&Increase priority\tCtrl+I'), 1485 helpText=help.taskIncreasePriority, bitmap='incpriority', 1486 *args, **kwargs)
1487
1488 - def doCommand(self, event):
1489 incPriority = command.IncPriorityCommand(self.taskList, 1490 self.viewer.curselection()) 1491 incPriority.do()
1492
1493 1494 -class TaskDecPriority(mixin_uicommand.NeedsSelectedTasksMixin, TaskListCommand, 1495 ViewerCommand):
1496 - def __init__(self, *args, **kwargs):
1497 super(TaskDecPriority, self).__init__( 1498 menuText=_('&Decrease priority\tCtrl+D'), 1499 helpText=help.taskDecreasePriority, bitmap='decpriority', 1500 *args, **kwargs)
1501
1502 - def doCommand(self, event):
1503 decPriority = command.DecPriorityCommand(self.taskList, 1504 self.viewer.curselection()) 1505 decPriority.do()
1506
1507 1508 -class DragAndDropCommand(ViewerCommand):
1509 - def onCommandActivate(self, dropItem, dragItems, part): # pylint: disable=W0221
1510 ''' Override onCommandActivate to be able to accept two items instead 1511 of one event. ''' 1512 self.doCommand(dropItem, dragItems, part)
1513
1514 - def doCommand(self, dropItem, dragItems, part): # pylint: disable=W0221
1515 dragAndDropCommand = self.createCommand(dropItem=dropItem, dragItems=dragItems, part=part) 1516 if dragAndDropCommand.canDo(): 1517 dragAndDropCommand.do() 1518
1519 - def createCommand(self, dropItem, dragItems, part):
1520 raise NotImplementedError # pragma: no cover
1521
1522 1523 -class TaskDragAndDrop(DragAndDropCommand, TaskListCommand):
1524 - def createCommand(self, dropItem, dragItems, part):
1525 return command.DragAndDropTaskCommand(self.taskList, dragItems, 1526 drop=[dropItem], part=part)
1527
1528 1529 -class ToggleCategory(mixin_uicommand.NeedsSelectedCategorizableMixin, 1530 ViewerCommand):
1531 - def __init__(self, *args, **kwargs):
1532 self.category = kwargs.pop('category') 1533 subject = self.category.subject() 1534 # Would like to use wx.ITEM_RADIO for mutually exclusive categories, but 1535 # a menu with radio items always has to have at least of the items 1536 # checked, while we allow none of the mutually exclusive categories to 1537 # be checked. Dynamically changing between wx.ITEM_CHECK and 1538 # wx.ITEM_RADIO would be a work-around in theory, using wx.ITEM_CHECK 1539 # when none of the mutually exclusive categories is checked and 1540 # wx.ITEM_RADIO otherwise, but dynamically changing the type of menu 1541 # items isn't possible. Hence, we use wx.ITEM_CHECK, even for mutual 1542 # exclusive categories. 1543 kind = wx.ITEM_CHECK 1544 super(ToggleCategory, self).__init__(menuText='&' + subject.replace('&', '&&'), 1545 helpText=_('Toggle %s') % subject, kind=kind, *args, **kwargs)
1546
1547 - def doCommand(self, event):
1551
1552 - def onUpdateUI(self, event):
1553 super(ToggleCategory, self).onUpdateUI(event) 1554 if self.enabled(event): 1555 check = self.__all_selected_items_are_in_category() 1556 for menuItem in self.menuItems: 1557 menuItem.Check(check)
1558
1560 selected_items_in_category = [item for item in self.viewer.curselection() \ 1561 if self.category in item.categories()] 1562 return selected_items_in_category == self.viewer.curselection()
1563
1564 - def enabled(self, event):
1565 viewerHasSelection = super(ToggleCategory, self).enabled(event) 1566 if not viewerHasSelection or self.viewer.isShowingCategories(): 1567 return False 1568 mutual_exclusive_ancestors = [ancestor for ancestor in self.category.ancestors() \ 1569 if ancestor.isMutualExclusive()] 1570 for categorizable in self.viewer.curselection(): 1571 for ancestor in mutual_exclusive_ancestors: 1572 if ancestor not in categorizable.categories(): 1573 return False # Not all mutually exclusive ancestors are checked 1574 return True # All mutually exclusive ancestors are checked
1575
1576 1577 -class Mail(mixin_uicommand.NeedsSelectionMixin, ViewerCommand):
1578 - def __init__(self, *args, **kwargs):
1579 menuText = _('&Mail...\tShift-Ctrl-M') if operating_system.isMac() else _('&Mail...\tCtrl-M') 1580 super(Mail, self).__init__(menuText=menuText, 1581 helpText=help.mailItem, bitmap='envelope_icon', *args, **kwargs)
1582
1583 - def doCommand(self, event, mail=sendMail, showerror=wx.MessageBox): # pylint: disable=W0221
1584 items = self.viewer.curselection() 1585 subject = self.subject(items) 1586 body = self.body(items) 1587 self.mail(subject, body, mail, showerror)
1588
1589 - def subject(self, items):
1590 assert items 1591 if len(items) > 2: 1592 return _('Several things') 1593 elif len(items) == 2: 1594 subjects = [item.subject(recursive=True) for item in items] 1595 return ' '.join([subjects[0], _('and'), subjects[1]]) 1596 else: 1597 return items[0].subject(recursive=True)
1598
1599 - def body(self, items):
1600 if len(items) > 1: 1601 bodyLines = [] 1602 for item in items: 1603 bodyLines.extend(self.itemToLines(item)) 1604 else: 1605 bodyLines = items[0].description().splitlines() 1606 return '\r\n'.join(bodyLines)
1607
1608 - def itemToLines(self, item):
1609 lines = [] 1610 subject = item.subject(recursive=True) 1611 lines.append(subject) 1612 if item.description(): 1613 lines.extend(item.description().splitlines()) 1614 lines.extend('\r\n') 1615 return lines
1616
1617 - def mail(self, subject, body, mail, showerror):
1618 try: 1619 mail('', subject, body) 1620 except: 1621 # Try again with a dummy recipient: 1622 try: 1623 mail('recipient@domain.com', subject, body) 1624 except Exception, reason: # pylint: disable=W0703 1625 showerror(_('Cannot send email:\n%s') % ExceptionAsUnicode(reason), 1626 caption=_('%s mail error') % meta.name, 1627 style=wx.ICON_ERROR)
1628
1629 1630 -class AddNote(mixin_uicommand.NeedsSelectedNoteOwnersMixin, ViewerCommand, 1631 settings_uicommand.SettingsCommand):
1632 - def __init__(self, *args, **kwargs):
1633 super(AddNote, self).__init__(menuText=_('Add &note...\tCtrl+B'), 1634 helpText=help.addNote, bitmap='new', *args, **kwargs)
1635
1636 - def doCommand(self, event, show=True): # pylint: disable=W0221
1637 addNoteCommand = command.AddNoteCommand(self.viewer.presentation(), 1638 self.viewer.curselection()) 1639 addNoteCommand.do() 1640 editDialog = dialog.editor.NoteEditor(self.mainWindow(), 1641 addNoteCommand.items, self.settings, self.viewer.presentation(), 1642 self.mainWindow().taskFile, bitmap=self.bitmap) 1643 editDialog.Show(show) 1644 return editDialog # for testing purposes
1645
1646 1647 -class OpenAllNotes(mixin_uicommand.NeedsSelectedNoteOwnersMixinWithNotes, 1648 ViewerCommand, settings_uicommand.SettingsCommand):
1649 - def __init__(self, *args, **kwargs):
1650 super(OpenAllNotes, self).__init__(menuText=_('Open all notes...\tShift+Ctrl+B'), 1651 helpText=help.openAllNotes, bitmap='edit', *args, **kwargs)
1652
1653 - def doCommand(self, event):
1654 for item in self.viewer.curselection(): 1655 for note in item.notes(): 1656 editDialog = dialog.editor.NoteEditor(self.mainWindow(), 1657 [note], self.settings, self.viewer.presentation(), 1658 self.mainWindow().taskFile, bitmap=self.bitmap) 1659 editDialog.Show()
1660
1661 1662 -class EffortNew(mixin_uicommand.NeedsAtLeastOneTaskMixin, ViewerCommand, 1663 EffortListCommand, TaskListCommand, 1664 settings_uicommand.SettingsCommand):
1665 - def __init__(self, *args, **kwargs):
1666 effortList = kwargs['effortList'] 1667 super(EffortNew, self).__init__(bitmap='new', 1668 menuText=effortList.newItemMenuText, 1669 helpText=effortList.newItemHelpText, *args, **kwargs)
1670
1671 - def doCommand(self, event, show=True):
1672 if self.viewer and self.viewer.isShowingTasks() and self.viewer.curselection(): 1673 selectedTasks = self.viewer.curselection() 1674 elif self.viewer and self.viewer.isShowingEffort(): 1675 selectedEfforts = self.viewer.curselection() 1676 if selectedEfforts: 1677 selectedTasks = [selectedEfforts[0].task()] 1678 else: 1679 selectedTasks = [self.firstTask(self.viewer.domainObjectsToView())] 1680 else: 1681 selectedTasks = [self.firstTask(self.taskList)] 1682 1683 newEffortCommand = command.NewEffortCommand(self.effortList, selectedTasks) 1684 newEffortCommand.do() 1685 newEffortDialog = dialog.editor.EffortEditor(self.mainWindow(), 1686 newEffortCommand.items, self.settings, self.effortList, 1687 self.mainWindow().taskFile, bitmap=self.bitmap) 1688 if show: 1689 newEffortDialog.Show() 1690 return newEffortDialog
1691 1692 @staticmethod
1693 - def firstTask(tasks):
1694 subjectDecoratedTasks = [(eachTask.subject(recursive=True), 1695 eachTask) for eachTask in tasks] 1696 subjectDecoratedTasks.sort() 1697 return subjectDecoratedTasks[0][1]
1698
1699 1700 -class EffortStart(mixin_uicommand.NeedsSelectedTasksMixin, ViewerCommand, 1701 TaskListCommand):
1702 ''' UICommand to start tracking effort for the selected task(s). ''' 1703
1704 - def __init__(self, *args, **kwargs):
1705 super(EffortStart, self).__init__(bitmap='clock_icon', 1706 menuText=_('&Start tracking effort\tCtrl-T'), 1707 helpText=help.effortStart, *args, **kwargs)
1708
1709 - def doCommand(self, event):
1710 start = command.StartEffortCommand(self.taskList, 1711 self.viewer.curselection()) 1712 start.do()
1713
1714 - def enabled(self, event):
1715 return super(EffortStart, self).enabled(event) and \ 1716 any(not task.completed() and not task.isBeingTracked() \ 1717 for task in self.viewer.curselection())
1718
1719 1720 -class EffortStartForEffort(mixin_uicommand.NeedsSelectedEffortMixin, 1721 ViewerCommand, TaskListCommand):
1722 ''' UICommand to start tracking for the task(s) of selected effort(s). ''' 1723
1724 - def __init__(self, *args, **kwargs):
1725 super(EffortStartForEffort, self).__init__(bitmap='clock_icon', 1726 menuText=_('&Start tracking effort'), 1727 helpText=_( 1728 'Start tracking effort for the task(s) of the selected effort(s)'), 1729 *args, **kwargs)
1730
1731 - def doCommand(self, event):
1732 start = command.StartEffortCommand(self.taskList, self.trackableTasks()) 1733 start.do()
1734
1735 - def enabled(self, event):
1736 return super(EffortStartForEffort, self).enabled(event) and \ 1737 self.trackableTasks()
1738
1739 - def trackableTasks(self):
1740 tasks = set([effort.task() for effort in self.viewer.curselection()]) 1741 return [task for task in tasks if not task.completed() \ 1742 and not task.isBeingTracked()]
1743
1744 1745 -class EffortStartForTask(TaskListCommand):
1746 ''' UICommand to start tracking for a specific task. This command can 1747 be used to build a menu with separate menu items for all tasks. 1748 See gui.menu.StartEffortForTaskMenu. ''' 1749
1750 - def __init__(self, *args, **kwargs):
1751 self.task = kwargs.pop('task') 1752 subject = self.task.subject() or _('(No subject)') 1753 super(EffortStartForTask, self).__init__( \ 1754 bitmap=self.task.icon(recursive=True), menuText='&' + subject.replace('&', '&&'), 1755 helpText=_('Start tracking effort for %s') % subject, 1756 *args, **kwargs)
1757
1758 - def doCommand(self, event):
1759 start = command.StartEffortCommand(self.taskList, [self.task]) 1760 start.do()
1761
1762 - def enabled(self, event):
1763 return not self.task.isBeingTracked() and not self.task.completed()
1764
1765 1766 -class EffortStartButton(mixin_uicommand.PopupButtonMixin, TaskListCommand):
1767 - def __init__(self, *args, **kwargs):
1768 kwargs['taskList'] = base.filter.DeletedFilter(kwargs['taskList']) 1769 super(EffortStartButton, self).__init__(bitmap='clock_menu_icon', 1770 menuText=_('&Start tracking effort'), 1771 helpText=_( 1772 'Select a task via the menu and start tracking effort for it'), 1773 *args, **kwargs)
1774
1775 - def createPopupMenu(self):
1776 from taskcoachlib.gui import menu 1777 1778 return menu.StartEffortForTaskMenu(self.mainWindow(), self.taskList)
1779
1780 - def enabled(self, event):
1781 return any(not task.completed() for task in self.taskList)
1782
1783 1784 -class EffortStop(EffortListCommand, TaskListCommand, ViewerCommand):
1785 defaultMenuText = _('Stop tracking or resume tracking effort\tShift+Ctrl+T') 1786 defaultHelpText = help.effortStopOrResume 1787 stopMenuText = _('St&op tracking %s\tShift+Ctrl+T') 1788 stopHelpText = _('Stop tracking effort for the active task(s)') 1789 resumeMenuText = _('&Resume tracking %s\tShift+Ctrl+T') 1790 resumeHelpText = _('Resume tracking effort for the last tracked task') 1791
1792 - def __init__(self, *args, **kwargs):
1793 super(EffortStop, self).__init__(bitmap='clock_resume_icon', 1794 bitmap2='clock_stop_icon', menuText=self.defaultMenuText, 1795 helpText=self.defaultHelpText, kind=wx.ITEM_CHECK, *args, **kwargs) 1796 self.__tracker = effort.EffortListTracker(self.effortList) 1797 self.__currentBitmap = None # Don't know yet what our bitmap is
1798
1799 - def efforts(self):
1800 selectedEfforts = set() 1801 for item in self.viewer.curselection(): 1802 if isinstance(item, task.Task): 1803 selectedEfforts |= set(item.efforts()) 1804 elif isinstance(item, effort.Effort): 1805 selectedEfforts.add(item) 1806 selectedEfforts &= set(self.__tracker.trackedEfforts()) 1807 return selectedEfforts if selectedEfforts else self.__tracker.trackedEfforts()
1808
1809 - def doCommand(self, event=None):
1810 efforts = self.efforts() 1811 if efforts: 1812 # Stop the tracked effort(s) 1813 effortCommand = command.StopEffortCommand(self.effortList, efforts) 1814 else: 1815 # Resume tracking the last task 1816 effortCommand = command.StartEffortCommand(self.taskList, 1817 [self.mostRecentTrackedTask()]) 1818 effortCommand.do()
1819
1820 - def enabled(self, event=None):
1821 # If there are tracked efforts this command will stop them. If there are 1822 # untracked efforts this command will resume them. Otherwise this 1823 # command is disabled. 1824 return self.anyTrackedEfforts() or self.anyStoppedEfforts()
1825
1826 - def onUpdateUI(self, event):
1827 super(EffortStop, self).onUpdateUI(event) 1828 self.updateUI()
1829
1830 - def updateUI(self):
1831 paused = self.anyStoppedEfforts() and not self.anyTrackedEfforts() 1832 self.updateToolState(not paused) 1833 bitmapName = self.bitmap if paused else self.bitmap2 1834 menuText = self.getMenuText(paused) 1835 if (bitmapName != self.__currentBitmap) or bool( 1836 [item for item in self.menuItems if item.GetItemLabel() != menuText]): 1837 self.__currentBitmap = bitmapName 1838 self.updateToolBitmap(bitmapName) 1839 self.updateToolHelp() 1840 self.updateMenuItems(paused)
1841
1842 - def updateToolState(self, paused):
1843 if not self.toolbar: 1844 return # Toolbar is hidden 1845 if paused != self.toolbar.GetToolState(self.id): 1846 self.toolbar.ToggleTool(self.id, paused)
1847
1848 - def updateToolBitmap(self, bitmapName):
1849 if not self.toolbar: 1850 return # Toolbar is hidden 1851 bitmap = wx.ArtProvider_GetBitmap(bitmapName, wx.ART_TOOLBAR, 1852 self.toolbar.GetToolBitmapSize()) 1853 # On wxGTK, changing the bitmap doesn't work when the tool is 1854 # disabled, so we first enable it if necessary: 1855 disable = False 1856 if not self.toolbar.GetToolEnabled(self.id): 1857 self.toolbar.EnableTool(self.id, True) 1858 disable = True 1859 self.toolbar.SetToolNormalBitmap(self.id, bitmap) 1860 if disable: 1861 self.toolbar.EnableTool(self.id, False) 1862 self.toolbar.Realize()
1863
1864 - def updateMenuItems(self, paused):
1865 menuText = self.getMenuText(paused) 1866 helpText = self.getHelpText(paused) 1867 for menuItem in self.menuItems: 1868 menuItem.Check(paused) 1869 menuItem.SetItemLabel(menuText) 1870 menuItem.SetHelp(helpText)
1871
1872 - def getMenuText(self, paused=None): # pylint: disable=W0221
1873 if self.anyTrackedEfforts(): 1874 trackedEfforts = list(self.efforts()) 1875 subject = _('multiple tasks') if len(trackedEfforts) > 1 \ 1876 else trackedEfforts[0].task().subject() 1877 return self.stopMenuText % self.trimmedSubject(subject) 1878 if paused is None: 1879 paused = self.anyStoppedEfforts() 1880 if paused: 1881 return self.resumeMenuText % \ 1882 self.trimmedSubject(self.mostRecentTrackedTask().subject()) 1883 else: 1884 return self.defaultMenuText
1885
1886 - def getHelpText(self, paused=None): # pylint: disable=W0221
1887 if self.anyTrackedEfforts(): 1888 return self.stopHelpText 1889 if paused is None: 1890 paused = self.anyStoppedEfforts() 1891 return self.resumeHelpText if paused else self.defaultHelpText 1892
1893 - def anyStoppedEfforts(self):
1894 return bool(self.effortList.maxDateTime())
1895
1896 - def anyTrackedEfforts(self):
1897 return bool(self.efforts())
1898
1899 - def mostRecentTrackedTask(self):
1900 stopTimes = [(effort.getStop(), effort) for effort in self.effortList if effort.getStop() is not None] 1901 return max(stopTimes)[1].task()
1902 1903 @staticmethod
1904 - def trimmedSubject(subject, maxLength=35, postFix='...'):
1905 trim = len(subject) > maxLength 1906 return subject[:maxLength - len(postFix)] + postFix if trim else subject
1907
1908 1909 -class CategoryNew(CategoriesCommand, settings_uicommand.SettingsCommand):
1910 - def __init__(self, *args, **kwargs):
1911 super(CategoryNew, self).__init__(bitmap='new', 1912 menuText=_('New category...\tCtrl-G'), 1913 helpText=help.categoryNew, *args, **kwargs)
1914
1915 - def doCommand(self, event, show=True): # pylint: disable=W0221
1916 newCategoryCommand = command.NewCategoryCommand(self.categories) 1917 newCategoryCommand.do() 1918 taskFile = self.mainWindow().taskFile 1919 newCategoryDialog = dialog.editor.CategoryEditor(self.mainWindow(), 1920 newCategoryCommand.items, self.settings, taskFile.categories(), 1921 taskFile, bitmap=self.bitmap) 1922 newCategoryDialog.Show(show)
1923
1924 1925 -class CategoryDragAndDrop(DragAndDropCommand, CategoriesCommand):
1926 - def createCommand(self, dropItem, dragItems, part):
1927 return command.DragAndDropCategoryCommand(self.categories, dragItems, 1928 drop=[dropItem], part=part)
1929
1930 1931 -class NoteNew(NotesCommand, settings_uicommand.SettingsCommand, ViewerCommand):
1932 menuText = _('New note...\tCtrl-J') 1933 helpText = help.noteNew 1934
1935 - def __init__(self, *args, **kwargs):
1936 super(NoteNew, self).__init__(menuText=self.menuText, 1937 helpText=self.helpText, bitmap='new', *args, **kwargs)
1938
1939 - def doCommand(self, event, show=True): # pylint: disable=W0221
1940 if self.viewer and self.viewer.isShowingNotes(): 1941 noteDialog = self.viewer.newItemDialog(bitmap=self.bitmap) 1942 else: 1943 newNoteCommand = command.NewNoteCommand(self.notes, 1944 categories=self.categoriesForTheNewNote()) 1945 newNoteCommand.do() 1946 noteDialog = dialog.editor.NoteEditor(self.mainWindow(), 1947 newNoteCommand.items, self.settings, self.notes, 1948 self.mainWindow().taskFile, bitmap=self.bitmap) 1949 noteDialog.Show(show) 1950 return noteDialog # for testing purposes
1951
1952 - def categoriesForTheNewNote(self):
1953 return self.mainWindow().taskFile.categories().filteredCategories()
1954
1955 1956 -class NewNoteWithSelectedCategories(NoteNew, ViewerCommand):
1957 menuText = _('New &note with selected categories...') 1958 helpText = _('Insert a new note with the selected categories checked') 1959
1960 - def categoriesForTheNewNote(self):
1961 return self.viewer.curselection()
1962
1963 1964 -class NoteDragAndDrop(DragAndDropCommand, NotesCommand):
1965 - def createCommand(self, dropItem, dragItems, part):
1966 return command.DragAndDropNoteCommand(self.notes, dragItems, 1967 drop=[dropItem], part=part)
1968
1969 1970 -class AttachmentNew(AttachmentsCommand, ViewerCommand, 1971 settings_uicommand.SettingsCommand):
1972 - def __init__(self, *args, **kwargs):
1973 attachments = kwargs['attachments'] 1974 if 'menuText' not in kwargs: 1975 kwargs['menuText'] = attachments.newItemMenuText 1976 kwargs['helpText'] = attachments.newItemHelpText 1977 super(AttachmentNew, self).__init__(bitmap='new', *args, **kwargs)
1978
1979 - def doCommand(self, event, show=True): # pylint: disable=W0221
1980 attachmentDialog = self.viewer.newItemDialog(bitmap=self.bitmap) 1981 attachmentDialog.Show(show) 1982 return attachmentDialog # for testing purposes
1983
1984 1985 -class AddAttachment(mixin_uicommand.NeedsSelectedAttachmentOwnersMixin, 1986 ViewerCommand, settings_uicommand.SettingsCommand):
1987 - def __init__(self, *args, **kwargs):
1988 super(AddAttachment, self).__init__( \ 1989 menuText=_('&Add attachment...\tShift-Ctrl-A'), 1990 helpText=help.addAttachment, bitmap='paperclip_icon', 1991 *args, **kwargs)
1992
1993 - def doCommand(self, event):
1994 filename = widgets.AttachmentSelector() 1995 if not filename: 1996 return 1997 attachmentBase = self.settings.get('file', 'attachmentbase') 1998 if attachmentBase: 1999 filename = attachment.getRelativePath(filename, attachmentBase) 2000 addAttachmentCommand = command.AddAttachmentCommand( \ 2001 self.viewer.presentation(), self.viewer.curselection(), 2002 attachments=[attachment.FileAttachment(filename)]) 2003 addAttachmentCommand.do()
2004
2005 2006 -def openAttachments(attachments, settings, showerror):
2007 attachmentBase = settings.get('file', 'attachmentbase') 2008 for eachAttachment in attachments: 2009 try: 2010 eachAttachment.open(attachmentBase) 2011 except Exception, instance: # pylint: disable=W0703 2012 showerror(render.exception(Exception, instance), 2013 caption=_('Error opening attachment'), 2014 style=wx.ICON_ERROR)
2015
2016 2017 -class AttachmentOpen(mixin_uicommand.NeedsSelectedAttachmentsMixin, 2018 ViewerCommand, AttachmentsCommand, 2019 settings_uicommand.SettingsCommand):
2020 - def __init__(self, *args, **kwargs):
2021 attachments = kwargs['attachments'] 2022 super(AttachmentOpen, self).__init__(bitmap='fileopen', 2023 menuText=attachments.openItemMenuText, 2024 helpText=attachments.openItemHelpText, *args, **kwargs)
2025
2026 - def doCommand(self, event, showerror=wx.MessageBox): # pylint: disable=W0221
2027 openAttachments(self.viewer.curselection(), self.settings, showerror)
2028
2029 2030 -class OpenAllAttachments(mixin_uicommand.NeedsSelectionWithAttachmentsMixin, 2031 ViewerCommand, settings_uicommand.SettingsCommand):
2032 - def __init__(self, *args, **kwargs):
2033 super(OpenAllAttachments, self).__init__( \ 2034 menuText=_('&Open all attachments...\tShift+Ctrl+O'), 2035 helpText=help.openAllAttachments, bitmap='paperclip_icon', 2036 *args, **kwargs)
2037
2038 - def doCommand(self, event, showerror=wx.MessageBox): # pylint: disable=W0221
2039 allAttachments = [] 2040 for item in self.viewer.curselection(): 2041 allAttachments.extend(item.attachments()) 2042 openAttachments(allAttachments, self.settings, showerror)
2043
2044 2045 -class DialogCommand(base_uicommand.UICommand):
2046 - def __init__(self, *args, **kwargs):
2047 self._dialogTitle = kwargs.pop('dialogTitle') 2048 self._dialogText = kwargs.pop('dialogText') 2049 self._direction = kwargs.pop('direction', None) 2050 self.closed = True 2051 super(DialogCommand, self).__init__(*args, **kwargs)
2052
2053 - def doCommand(self, event):
2054 self.closed = False 2055 # pylint: disable=W0201 2056 self.dialog = widgets.HTMLDialog(self._dialogTitle, self._dialogText, 2057 bitmap=self.bitmap, 2058 direction=self._direction) 2059 for event in wx.EVT_CLOSE, wx.EVT_BUTTON: 2060 self.dialog.Bind(event, self.onClose) 2061 self.dialog.Show()
2062
2063 - def onClose(self, event):
2064 self.closed = True 2065 self.dialog.Destroy() 2066 event.Skip()
2067
2068 - def enabled(self, event):
2069 return self.closed
2070
2071 2072 -class Help(DialogCommand):
2073 - def __init__(self, *args, **kwargs):
2074 if operating_system.isMac(): 2075 # Use default keyboard shortcut for Mac OS X: 2076 menuText = _('&Help contents\tCtrl+?') 2077 else: 2078 # Use a letter, because 'Ctrl-?' doesn't work on Windows: 2079 menuText = _('&Help contents\tCtrl+H') 2080 super(Help, self).__init__(menuText=menuText, helpText=help.help, 2081 bitmap='led_blue_questionmark_icon', dialogTitle=_('Help'), 2082 dialogText=help.helpHTML, id=wx.ID_HELP, *args, **kwargs)
2083
2084 2085 -class Tips(settings_uicommand.SettingsCommand):
2086 - def __init__(self, *args, **kwargs):
2087 super(Tips, self).__init__(menuText=_('&Tips'), 2088 helpText=_('Tips about the program'), 2089 bitmap='lamp_icon', *args, **kwargs)
2090
2091 - def doCommand(self, event):
2092 help.showTips(self.mainWindow(), self.settings)
2093
2094 2095 -class Anonymize(IOCommand):
2096 - def __init__(self, *args, **kwargs):
2097 super(Anonymize, self).__init__(menuText=_('Anonymize'), 2098 helpText=_('Anonymize a task file to attach it to a bug report'), 2099 *args, **kwargs)
2100
2101 - def doCommand(self, event):
2102 anonymized_filename = anonymize(self.iocontroller.filename()) 2103 wx.MessageBox(_('Your task file has been anonymized and saved to:') \ 2104 + '\n' + anonymized_filename, _('Finished'), wx.OK)
2105
2106 - def enabled(self, event):
2107 return bool(self.iocontroller.filename())
2108
2109 2110 -class HelpAbout(DialogCommand):
2111 - def __init__(self, *args, **kwargs):
2112 super(HelpAbout, self).__init__(menuText=_('&About %s') % meta.name, 2113 helpText=_('Version and contact information about %s') % meta.name, 2114 dialogTitle=_('About %s') % meta.name, 2115 dialogText=help.aboutHTML, id=wx.ID_ABOUT, 2116 bitmap='led_blue_information_icon', *args, **kwargs)
2117
2118 2119 -class HelpLicense(DialogCommand):
2120 - def __init__(self, *args, **kwargs):
2121 super(HelpLicense, self).__init__(menuText=_('&License'), 2122 helpText=_('%s license') % meta.name, 2123 dialogTitle=_('%s license') % meta.name, 2124 dialogText=meta.licenseHTML, direction=wx.Layout_LeftToRight, 2125 bitmap='document_icon', *args, **kwargs)
2126
2127 2128 -class CheckForUpdate(settings_uicommand.SettingsCommand):
2129 - def __init__(self, *args, **kwargs):
2130 super(CheckForUpdate, self).__init__(menuText=_('Check for update'), 2131 helpText=_( 2132 'Check for the availability of a new version of %s') % meta.name, 2133 bitmap='box_icon', *args, **kwargs)
2134
2135 - def doCommand(self, event):
2136 meta.VersionChecker(self.settings, verbose=True).start()
2137
2138 2139 -class URLCommand(base_uicommand.UICommand):
2140 - def __init__(self, *args, **kwargs):
2141 self.url = kwargs.pop('url') 2142 super(URLCommand, self).__init__(*args, **kwargs)
2143
2144 - def doCommand(self, event):
2145 try: 2146 desktop.open(self.url) 2147 except Exception, reason: 2148 wx.MessageBox(_('Cannot open URL:\n%s') % ExceptionAsUnicode(reason), 2149 caption=_('%s URL error') % meta.name, 2150 style=wx.ICON_ERROR)
2151
2152 2153 -class FAQ(URLCommand):
2154 - def __init__(self, *args, **kwargs):
2155 super(FAQ, self).__init__(menuText=_('&Frequently asked questions'), 2156 helpText=_('Browse the frequently asked questions and answers'), 2157 bitmap='led_blue_questionmark_icon', url=meta.faq_url, 2158 *args, **kwargs)
2159
2160 2161 -class ReportBug(URLCommand):
2162 - def __init__(self, *args, **kwargs):
2163 super(ReportBug, self).__init__(menuText=_('Report a &bug...'), 2164 helpText=_('Report a bug or browse known bugs'), 2165 bitmap='bug_icon', url=meta.known_bugs_url, *args, **kwargs)
2166
2167 2168 -class RequestFeature(URLCommand):
2169 - def __init__(self, *args, **kwargs):
2170 super(RequestFeature, self).__init__( \ 2171 menuText=_('Request a &feature...'), 2172 helpText=_('Request a new feature or vote for existing requests'), 2173 bitmap='cogwheel_icon', url=meta.feature_request_url, 2174 *args, **kwargs)
2175
2176 2177 -class RequestSupport(URLCommand):
2178 - def __init__(self, *args, **kwargs):
2179 super(RequestSupport, self).__init__(menuText=_('Request &support...'), 2180 helpText=_('Request user support from the developers'), 2181 bitmap='life_ring_icon', url=meta.support_request_url, 2182 *args, **kwargs)
2183
2184 2185 -class HelpTranslate(URLCommand):
2186 - def __init__(self, *args, **kwargs):
2187 super(HelpTranslate, self).__init__( \ 2188 menuText=_('Help improve &translations...'), 2189 helpText=_('Help improve the translations of %s') % meta.name, 2190 bitmap='person_talking_icon', url=meta.translations_url, 2191 *args, **kwargs)
2192 2199
2200 2201 -class MainWindowRestore(base_uicommand.UICommand):
2202 - def __init__(self, *args, **kwargs):
2203 super(MainWindowRestore, self).__init__(menuText=_('&Restore'), 2204 helpText=_('Restore the window to its previous state'), 2205 bitmap='restore', *args, **kwargs)
2206
2207 - def doCommand(self, event):
2208 self.mainWindow().restore(event)
2209
2210 2211 -class Search(ViewerCommand, settings_uicommand.SettingsCommand):
2212 # Search can only be attached to a real viewer, not to a viewercontainer
2213 - def __init__(self, *args, **kwargs):
2214 self.__bound = False 2215 super(Search, self).__init__(*args, helpText=_('Search'), **kwargs) 2216 assert self.viewer.isSearchable()
2217
2218 - def onFind(self, searchString, matchCase, includeSubItems, 2219 searchDescription, regularExpression):
2220 if self.__bound: 2221 self.viewer.setSearchFilter(searchString, matchCase, includeSubItems, 2222 searchDescription, regularExpression)
2223
2224 - def appendToToolBar(self, toolbar):
2225 self.__bound = True 2226 searchString, matchCase, includeSubItems, searchDescription, regularExpression = \ 2227 self.viewer.getSearchFilter() 2228 #pylint: disable=W0201 2229 self.searchControl = widgets.SearchCtrl(toolbar, value=searchString, 2230 style=wx.TE_PROCESS_ENTER, matchCase=matchCase, 2231 includeSubItems=includeSubItems, 2232 searchDescription=searchDescription, 2233 regularExpression=regularExpression, 2234 callback=self.onFind) 2235 toolbar.AddControl(self.searchControl) 2236 self.bindKeyDownInViewer() 2237 self.bindKeyDownInSearchCtrl()
2238
2239 - def bindKeyDownInViewer(self):
2240 ''' Bind wx.EVT_KEY_DOWN to self.onViewerKeyDown so we can catch 2241 Ctrl-F. ''' 2242 widget = self.viewer.getWidget() 2243 try: 2244 window = widget.GetMainWindow() 2245 except AttributeError: 2246 window = widget 2247 window.Bind(wx.EVT_KEY_DOWN, self.onViewerKeyDown)
2248
2249 - def bindKeyDownInSearchCtrl(self):
2250 ''' Bind wx.EVT_KEY_DOWN to self.onSearchCtrlKeyDown so we can catch 2251 the Escape key and drop down the menu on Ctrl-Down. ''' 2252 self.searchControl.getTextCtrl().Bind(wx.EVT_KEY_DOWN, 2253 self.onSearchCtrlKeyDown)
2254
2255 - def unbind(self, window, id_):
2256 self.__bound = False 2257 super(Search, self).unbind(window, id_)
2258
2259 - def onViewerKeyDown(self, event):
2260 ''' On Ctrl-F, move focus to the search control. ''' 2261 if event.KeyCode == ord('F') and event.CmdDown() and \ 2262 not event.AltDown(): 2263 self.searchControl.SetFocus() 2264 else: 2265 event.Skip()
2266
2267 - def onSearchCtrlKeyDown(self, event):
2268 ''' On Escape, move focus to the viewer, on Ctrl-Down popup the 2269 menu. ''' 2270 if event.KeyCode == wx.WXK_ESCAPE: 2271 self.viewer.SetFocus() 2272 elif event.KeyCode == wx.WXK_DOWN and event.AltDown(): 2273 self.searchControl.PopupMenu() 2274 else: 2275 event.Skip()
2276
2277 - def doCommand(self, event):
2278 pass # Not used
2279
2280 2281 -class QuickAdd(TaskListCommand, ViewerCommand, settings_uicommand.SettingsCommand):
2282 # Search can only be attached to a real viewer, not to a viewercontainer
2283 - def __init__(self, *args, **kwargs):
2284 self.__bound = False 2285 super(QuickAdd, self).__init__(*args, helpText=_('QuickAdd'), **kwargs)
2286
2287 - def appendToToolBar(self, toolbar):
2288 self.__bound = True 2289 self.QuickAddControl = wx.TextCtrl(toolbar, -1, "", style=wx.TE_PROCESS_ENTER) 2290 toolbar.AddControl(self.QuickAddControl) 2291 self.bindKeyDownEnter() 2292 self.bindKeyDownInSearchCtrl()
2293
2294 - def bindKeyDownInSearchCtrl(self):
2295 ''' Bind wx.EVT_KEY_DOWN to self.onSearchCtrlKeyDown so we can catch 2296 the Escape key''' 2297 self.QuickAddControl.Bind(wx.EVT_KEY_DOWN, self.onSearchCtrlKeyDown)
2298
2299 - def bindKeyDownEnter(self):
2300 ''' Bind wx.EVT_KEY_DOWN to self.onViewerKeyDown so we can catch 2301 Ctrl-F. ''' 2302 self.QuickAddControl.Bind(wx.EVT_KEY_DOWN, self.onEnterKey)
2303
2304 - def unbind(self, window, id_):
2305 self.__bound = False 2306 super(QuickAdd, self).unbind(window, id_)
2307
2308 - def onSearchCtrlKeyDown(self, event):
2309 ''' On Escape, move focus to the viewer''' 2310 if event.KeyCode == wx.WXK_ESCAPE: 2311 self.viewer.SetFocus() 2312 else: 2313 event.Skip()
2314
2315 - def onEnterKey(self, event):
2316 if event.KeyCode == wx.WXK_RETURN: 2317 taskArgs = command.Parser().getAnswers(self.QuickAddControl.GetValue()) 2318 categories = [] 2319 2320 for categoryName in taskArgs['Categories']: 2321 category = self.mainWindow().taskFile.categories().findCategoryByName(categoryName) 2322 if category is None: 2323 newCategoryCommand = command.NewCategoryCommand(self.mainWindow().taskFile.categories(), 2324 subject=categoryName) 2325 newCategoryCommand.do() 2326 categories.append(self.mainWindow().taskFile.categories().findCategoryByName(categoryName)) 2327 else: 2328 categories.append(self.mainWindow().taskFile.categories().findCategoryByName(categoryName)) 2329 2330 newTaskCommand = command.NewTaskCommand(self.mainWindow().taskFile.tasks(), 2331 subject=taskArgs['Title'], description=taskArgs['Description'], 2332 plannedStartDateTime=taskArgs['StartDate'], 2333 dueDateTime=taskArgs['EndDate'], 2334 priority=taskArgs['Priority'], 2335 actualStartDateTime=taskArgs['ActualStartDate'], 2336 completionDateTime=taskArgs['CompletionDate'], 2337 categories=categories) 2338 newTaskCommand.do() 2339 print self.settings.get('file', 'recentfiles') 2340 self.QuickAddControl.Clear() 2341 else: 2342 event.Skip()
2343
2344 - def doCommand(self, event):
2345 pass # Not used
2346
2347 2348 -class ToolbarChoiceCommandMixin(object):
2349 - def __init__(self, *args, **kwargs):
2350 self.choiceCtrl = None 2351 super(ToolbarChoiceCommandMixin, self).__init__(*args, **kwargs)
2352
2353 - def appendToToolBar(self, toolbar):
2354 ''' Add our choice control to the toolbar. ''' 2355 # pylint: disable=W0201 2356 self.choiceCtrl = wx.Choice(toolbar, choices=self.choiceLabels) 2357 self.currentChoice = self.choiceCtrl.Selection 2358 self.choiceCtrl.Bind(wx.EVT_CHOICE, self.onChoice) 2359 toolbar.AddControl(self.choiceCtrl)
2360
2361 - def unbind(self, window, id_):
2362 if self.choiceCtrl is not None: 2363 self.choiceCtrl.Unbind(wx.EVT_CHOICE) 2364 self.choiceCtrl = None 2365 super(ToolbarChoiceCommandMixin, self).unbind(window, id_)
2366
2367 - def onChoice(self, event):
2368 ''' The user selected a choice from the choice control. ''' 2369 choiceIndex = event.GetInt() 2370 if choiceIndex == self.currentChoice: 2371 return 2372 self.currentChoice = choiceIndex 2373 self.doChoice(self.choiceData[choiceIndex])
2374
2375 - def doChoice(self, choice):
2376 raise NotImplementedError # pragma: no cover
2377
2378 - def doCommand(self, event):
2379 pass # Not used
2380
2381 - def setChoice(self, choice):
2382 ''' Programmatically set the current choice in the choice control. ''' 2383 if self.choiceCtrl is not None: 2384 index = self.choiceData.index(choice) 2385 self.choiceCtrl.Selection = index 2386 self.currentChoice = index
2387
2388 - def enable(self, enable=True):
2389 if self.choiceCtrl is not None: 2390 self.choiceCtrl.Enable(enable)
2391
2392 2393 -class EffortViewerAggregationChoice(ToolbarChoiceCommandMixin, 2394 settings_uicommand.SettingsCommand, 2395 ViewerCommand):
2396 choiceLabels = [_('Effort details'), _('Effort per day'), 2397 _('Effort per week'), _('Effort per month')] 2398 choiceData = ['details', 'day', 'week', 'month'] 2399
2400 - def __init__(self, **kwargs):
2401 super(EffortViewerAggregationChoice, self).__init__(helpText=_('Aggregation mode'), 2402 **kwargs)
2403
2404 - def appendToToolBar(self, *args, **kwargs):
2405 super(EffortViewerAggregationChoice, self).appendToToolBar(*args, 2406 **kwargs) 2407 self.setChoice(self.settings.gettext(self.viewer.settingsSection(), 2408 'aggregation')) 2409 pub.subscribe(self.on_setting_changed, 2410 'settings.%s.aggregation' % self.viewer.settingsSection())
2411
2412 - def doChoice(self, choice):
2413 self.settings.settext(self.viewer.settingsSection(), 'aggregation', 2414 choice)
2415
2416 - def on_setting_changed(self, value):
2417 self.setChoice(value)
2418
2419 2420 -class EffortViewerAggregationOption(settings_uicommand.UIRadioCommand, 2421 ViewerCommand):
2422 - def isSettingChecked(self):
2423 return self.settings.gettext(self.viewer.settingsSection(), 2424 'aggregation') == self.value
2425
2426 - def doCommand(self, event):
2427 self.settings.settext(self.viewer.settingsSection(), 'aggregation', 2428 self.value)
2429
2430 2431 -class TaskViewerTreeOrListChoice(ToolbarChoiceCommandMixin, 2432 settings_uicommand.UICheckCommand, 2433 ViewerCommand):
2434 choiceLabels = [_('Tree'), _('List')] 2435 choiceData = [True, False] 2436
2437 - def __init__(self, *args, **kwargs):
2438 super(TaskViewerTreeOrListChoice, self).__init__( \ 2439 menuText=self.choiceLabels[0], 2440 helpText=_('When checked, show tasks as tree, ' 2441 'otherwise show tasks as list'), *args, **kwargs)
2442
2443 - def appendToToolBar(self, *args, **kwargs):
2444 super(TaskViewerTreeOrListChoice, self).appendToToolBar(*args, **kwargs) 2445 self.setChoice(self.settings.getboolean(self.viewer.settingsSection(), 2446 'treemode')) 2447 pub.subscribe(self.on_setting_changed, 2448 'settings.%s.treemode' % self.viewer.settingsSection())
2449
2450 - def doChoice(self, choice):
2451 self.settings.setboolean(self.viewer.settingsSection(), 'treemode', 2452 choice)
2453
2454 - def on_setting_changed(self, value):
2455 self.setChoice(value)
2456
2457 2458 -class TaskViewerTreeOrListOption(settings_uicommand.UIRadioCommand, 2459 ViewerCommand):
2460 - def isSettingChecked(self):
2461 return self.settings.getboolean(self.viewer.settingsSection(), 2462 'treemode') == self.value
2463
2464 - def doCommand(self, event):
2465 self.settings.setboolean(self.viewer.settingsSection(), 'treemode', 2466 self.value)
2467
2468 2469 -class CategoryViewerFilterChoice(ToolbarChoiceCommandMixin, 2470 settings_uicommand.UICheckCommand):
2471 choiceLabels = [_('Filter on all checked categories'), 2472 _('Filter on any checked category')] 2473 choiceData = [True, False] 2474
2475 - def __init__(self, *args, **kwargs):
2476 super(CategoryViewerFilterChoice, self).__init__( \ 2477 menuText=self.choiceLabels[0], 2478 helpText=_('When checked, filter on all checked categories, ' 2479 'otherwise on any checked category'), *args, **kwargs)
2480
2481 - def appendToToolBar(self, *args, **kwargs):
2482 super(CategoryViewerFilterChoice, self).appendToToolBar(*args, **kwargs) 2483 pub.subscribe(self.on_setting_changed, 2484 'settings.view.categoryfiltermatchall')
2485
2486 - def isSettingChecked(self):
2487 return self.settings.getboolean('view', 'categoryfiltermatchall')
2488
2489 - def doChoice(self, choice):
2490 self.settings.setboolean('view', 'categoryfiltermatchall', choice)
2491
2492 - def doCommand(self, event):
2493 self.settings.setboolean('view', 'categoryfiltermatchall', 2494 self._isMenuItemChecked(event))
2495
2496 - def on_setting_changed(self, value):
2497 self.setChoice(value)
2498
2499 2500 -class SquareTaskViewerOrderChoice(ToolbarChoiceCommandMixin, 2501 settings_uicommand.SettingsCommand, 2502 ViewerCommand):
2503 choiceLabels = [_('Budget'), _('Time spent'), _('Fixed fee'), _('Revenue'), 2504 _('Priority')] 2505 choiceData = ['budget', 'timeSpent', 'fixedFee', 'revenue', 'priority'] 2506
2507 - def __init__(self, **kwargs):
2508 super(SquareTaskViewerOrderChoice, self).__init__(helpText=_('Order choice'), **kwargs)
2509
2510 - def appendToToolBar(self, *args, **kwargs):
2511 super(SquareTaskViewerOrderChoice, self).appendToToolBar(*args, 2512 **kwargs) 2513 pub.subscribe(self.on_setting_changed, 2514 'settings.%s.sortby' % self.viewer.settingsSection())
2515
2516 - def doChoice(self, choice):
2517 self.settings.settext(self.viewer.settingsSection(), 'sortby', choice)
2518
2519 - def on_setting_changed(self, value):
2520 self.setChoice(value)
2521
2522 2523 -class SquareTaskViewerOrderByOption(settings_uicommand.UIRadioCommand, 2524 ViewerCommand):
2525 - def isSettingChecked(self):
2526 return self.settings.gettext(self.viewer.settingsSection(), 2527 'sortby') == self.value
2528
2529 - def doCommand(self, event):
2530 self.settings.settext(self.viewer.settingsSection(), 'sortby', 2531 self.value)
2532
2533 2534 -class CalendarViewerConfigure(ViewerCommand):
2535 menuText = _('&Configure') 2536 helpText = _('Configure the calendar viewer') 2537 bitmap = 'wrench_icon' 2538
2539 - def __init__(self, *args, **kwargs):
2540 super(CalendarViewerConfigure, self).__init__( \ 2541 menuText=self.menuText, helpText=self.helpText, bitmap=self.bitmap, 2542 *args, **kwargs)
2543
2544 - def doCommand(self, event):
2545 self.viewer.configure()
2546
2547 2548 -class CalendarViewerNavigationCommand(ViewerCommand):
2549 - def __init__(self, *args, **kwargs):
2550 super(CalendarViewerNavigationCommand, self).__init__( \ 2551 menuText=self.menuText, helpText=self.helpText, bitmap=self.bitmap, 2552 *args, **kwargs)
2553
2554 - def doCommand(self, event):
2555 self.viewer.freeze() 2556 try: 2557 self.viewer.SetViewType(self.calendarViewType) # pylint: disable=E1101 2558 finally: 2559 self.viewer.thaw()
2560
2561 2562 -class CalendarViewerNextPeriod(CalendarViewerNavigationCommand):
2563 menuText = _('&Next period') 2564 helpText = _('Show next period') 2565 bitmap = 'next' 2566 calendarViewType = wxSCHEDULER_NEXT
2567
2568 2569 -class CalendarViewerPreviousPeriod(CalendarViewerNavigationCommand):
2570 menuText = _('&Previous period') 2571 helpText = _('Show previous period') 2572 bitmap = 'prev' 2573 calendarViewType = wxSCHEDULER_PREV
2574
2575 2576 -class CalendarViewerToday(CalendarViewerNavigationCommand):
2577 menuText = _('&Today') 2578 helpText = _('Show today') 2579 bitmap = 'calendar_icon' 2580 calendarViewType = wxSCHEDULER_TODAY
2581
2582 2583 -class ToggleAutoColumnResizing(settings_uicommand.UICheckCommand, 2584 ViewerCommand):
2585 - def __init__(self, *args, **kwargs):
2586 super(ToggleAutoColumnResizing, self).__init__( \ 2587 menuText=_('&Automatic column resizing'), 2588 helpText=_('When checked, automatically resize columns to fill' 2589 ' available space'), 2590 *args, **kwargs) 2591 wx.CallAfter(self.updateWidget)
2592
2593 - def updateWidget(self):
2595
2596 - def isSettingChecked(self):
2597 return self.settings.getboolean(self.viewer.settingsSection(), 2598 'columnautoresizing')
2599
2600 - def doCommand(self, event):
2601 self.settings.set(self.viewer.settingsSection(), 'columnautoresizing', 2602 str(self._isMenuItemChecked(event))) 2603 self.updateWidget()
2604
2605 2606 -class ViewerPieChartAngle(ViewerCommand, settings_uicommand.SettingsCommand):
2607 - def __init__(self, *args, **kwargs):
2608 self.sliderCtrl = None 2609 super(ViewerPieChartAngle, self).__init__( \ 2610 helpText=_('Set pie chart angle'), *args, **kwargs)
2611
2612 - def appendToToolBar(self, toolbar):
2613 ''' Add our slider control to the toolbar. ''' 2614 # pylint: disable=W0201 2615 self.sliderCtrl = wx.Slider(toolbar, minValue=0, maxValue=90, 2616 value=self.getCurrentAngle(), size=(120, -1)) 2617 self.sliderCtrl.Bind(wx.EVT_SLIDER, self.onSlider) 2618 toolbar.AddControl(self.sliderCtrl)
2619
2620 - def unbind(self, window, itemId):
2621 if self.sliderCtrl is not None: 2622 self.sliderCtrl.Unbind(wx.EVT_SLIDER) 2623 self.sliderCtrl = None 2624 super(ViewerPieChartAngle, self).unbind(window, itemId)
2625
2626 - def onSlider(self, event):
2627 ''' The user picked a new angle. ''' 2628 event.Skip() 2629 self.setCurrentAngle()
2630
2631 - def doCommand(self, event):
2632 pass # Not used
2633
2634 - def getCurrentAngle(self):
2635 return self.settings.getint(self.viewer.settingsSection(), 2636 'piechartangle')
2637
2638 - def setCurrentAngle(self):
2639 if self.sliderCtrl is not None: 2640 self.settings.setint(self.viewer.settingsSection(), 'piechartangle', 2641 self.sliderCtrl.GetValue())
2642
2643 2644 -class RoundingPrecision(ToolbarChoiceCommandMixin, ViewerCommand, 2645 settings_uicommand.SettingsCommand):
2646 roundingChoices = (0, 1, 3, 5, 6, 10, 15, 20, 30, 60) # Minutes 2647 choiceData = [minutes * 60 for minutes in roundingChoices] # Seconds 2648 choiceLabels = [_('No rounding'), _('1 minute')] + \ 2649 [_('%d minutes') % minutes for minutes in roundingChoices[2:]] 2650
2651 - def __init__(self, **kwargs):
2652 super(RoundingPrecision, self).__init__(helpText=_('Rounding precision'), **kwargs)
2653
2654 - def doChoice(self, choice):
2655 self.settings.setint(self.viewer.settingsSection(), 'round', choice)
2656
2657 2658 -class RoundBy(settings_uicommand.UIRadioCommand, ViewerCommand):
2659 - def isSettingChecked(self):
2660 return self.settings.getint(self.viewer.settingsSection(), 2661 'round') == self.value
2662
2663 - def doCommand(self, event):
2664 self.settings.setint(self.viewer.settingsSection(), 'round', self.value)
2665
2666 2667 -class AlwaysRoundUp(settings_uicommand.UICheckCommand, ViewerCommand):
2668 - def __init__(self, *args, **kwargs):
2669 self.checkboxCtrl = None 2670 super(AlwaysRoundUp, self).__init__( \ 2671 menuText=_('&Always round up'), 2672 helpText=_('Always round up to the next rounding increment'), 2673 *args, **kwargs)
2674
2675 - def appendToToolBar(self, toolbar):
2676 ''' Add a checkbox control to the toolbar. ''' 2677 # pylint: disable=W0201 2678 self.checkboxCtrl = wx.CheckBox(toolbar, label=self.menuText) 2679 self.checkboxCtrl.Bind(wx.EVT_CHECKBOX, self.onCheck) 2680 toolbar.AddControl(self.checkboxCtrl)
2681
2682 - def unbind(self, window, itemId):
2683 if self.checkboxCtrl is not None: 2684 self.checkboxCtrl.Unbind(wx.EVT_CHECKBOX) 2685 self.checkboxCtrl = None 2686 super(AlwaysRoundUp, self).unbind(window, itemId)
2687
2688 - def isSettingChecked(self):
2689 return self.settings.getboolean(self.viewer.settingsSection(), 2690 'alwaysroundup')
2691
2692 - def onCheck(self, event):
2693 self.setSetting(event.IsChecked())
2694
2695 - def doCommand(self, event):
2696 self.setSetting(self._isMenuItemChecked(event))
2697
2698 - def setSetting(self, alwaysRoundUp):
2699 self.settings.setboolean(self.viewer.settingsSection(), 'alwaysroundup', 2700 alwaysRoundUp)
2701
2702 - def setValue(self, value):
2703 if self.checkboxCtrl is not None: 2704 self.checkboxCtrl.SetValue(value)
2705
2706 - def enable(self, enable=True):
2707 if self.checkboxCtrl is not None: 2708 self.checkboxCtrl.Enable(enable)
2709